代码之家  ›  专栏  ›  技术社区  ›  Dario

玩无限懒惰的算术

  •  6
  • Dario  · 技术社区  · 15 年前

    许多现代编程语言允许我们处理潜在的无限列表,并对它们执行某些操作。

    示例[python]:

    EvenSquareNumbers = ( x * x for x in naturals() if x mod 2 == 0 )
    

    这样的列表可以存在,因为只计算实际需要的元素。(懒惰的评价)

    我只是出于兴趣想知道是否有可能(甚至在某些语言中)将懒惰的评估机制扩展到算术。

    例子: 给出了无限的偶数列表 evens = [ x | x <- [1..], even x ] 我们无法计算

    length evens
    

    因为这里需要的计算永远不会终止。

    但我们可以确定

    length evens > 42
    

    不用评估整体 length 术语。

    用任何语言都可以吗?哈斯克尔呢?

    编辑:为了更清楚地说明这一点:问题不在于如何确定一个懒惰的列表是否比一个给定的数字短。它是关于使用传统的内置函数的一种方式,数值计算是懒惰地完成的。

    SDCVVC为Haskell提供了一种解决方案:

    data Nat = Zero | Succ Nat deriving (Show, Eq, Ord)
    
    toLazy :: Integer -> Nat
    toLazy 0 = Zero
    toLazy n = Succ (toLazy (n-1))
    
    instance Num Nat where
       (+) (Succ x) y = Succ (x + y)
       (+) Zero y = y
       (*) Zero y = Zero
       (*) x Zero = Zero
       (*) (Succ x) y = y + (x * y)
       fromInteger = toLazy
       abs = id
       negate = error "Natural only"
       signum Zero = Zero
       signum (Succ x) = Succ Zero
    
    len [] = Zero
    len (_:x') = Succ $ len x'
    
    -- Test 
    
    len [1..] < 42 
    

    这在其他语言中也是可能的吗?

    4 回复  |  直到 15 年前
        1
  •  8
  •   sdcvvc    15 年前

    你可以创建你自己的自然数…

    data Nat = Zero | Succ Nat deriving (Show, Eq, Ord)
    
    len :: [a] -> Nat
    len = foldr (const Succ) Zero
    
    toLazy :: Int -> Nat
    toLazy 0 = Zero
    toLazy n = Succ (toLazy (n-1))
    
    a = len [1..] > toLazy 42
    

    那么A是真的,多亏了懒惰的评价。Len使用folder很重要,foldl不适用于无限列表。你也可以做一些算术(没有测试):

    instance Num Nat where
       (+) (Succ x) y = Succ (x + y)
       (+) Zero y = y
       (*) Zero y = Zero
       (*) x Zero = Zero
       (*) (Succ x) y = y + (x * y)
       fromInteger = toLazy
       abs = id
       negate = error "Natural only"
       signum Zero = Zero
       signum (Succ x) = Succ Zero
    

    例如,2*Infinity+3是Infinity:

    let infinity = Succ infinity
    
    *Main> 2 * infinity + 3
    (Succ (Succ (Succ ^cCc (Succ (Succ (SuccInterrupted.
    

    二解

    另一种解决方案是使用()列表作为惰性自然数。

    len = map (const ())
    toLazy n = replicate n () 
    a = len [1..] > toLazy 42
    

    检查 Hackage .

    编辑:添加实例号。

    编辑2:

    将第二个解决方案转换为python:

    import itertools
    
    def length(x):
        return itertools.imap(lambda: (), x) 
    
    def to_lazy(n):
        return itertools.chain([()] * n)
    

    要添加数字,请使用itertools.chain;要进行乘法,请使用itertools.product。这不像哈斯凯尔那样漂亮,但很管用。

    在任何其他带有闭包的严格语言中,您都可以使用类型()->a而不是a来模拟惰性。这样就可以将第一个解决方案从haskell转换为其他语言。然而,这很快就会变得不可读。

    也见 a nice article on Python one-liners .

        2
  •  3
  •   Brian    15 年前

    如果不是因为懒惰,我认为这会在F中起作用:

    type Nat = Zero | Succ of Nat
    
    let rec toLazy x =
        if x = 0 then Zero
        else Succ(toLazy(x-1))
    
    type Nat with
        static member (+)(x:Nat, y:Nat) =
            match x with
            | Succ(a) -> Succ(a+y)
            | Zero -> y
        static member (*)(x:Nat, y:Nat) =
            match x,y with
            | _, Zero -> Zero
            | Zero, _ -> Zero
            | Succ(a), b -> b + a*b
    
    module NumericLiteralQ =
        let FromZero()          =  toLazy 0
        let FromOne()           =  toLazy 1
        let FromInt32(x:int)    =  toLazy x
    
    let rec len = function
        | [] -> 0Q
        | _::t -> 1Q + len t
    
    printfn "%A" (len [1..42] < 100Q)
    printfn "%A" (len [while true do yield 1] < 100Q)
    

    但由于我们需要懒惰,它扩展到:

    let eqLazy<'T> (x:Lazy<'T>) (y:Lazy<'T>) : bool =  //'
        let a = x.Force()
        let b = y.Force()
        a = b
    
    type Nat = Zero | Succ of LazyNat
    and [<StructuralComparison(false); StructuralEqualityAttribute(false)>]
        LazyNat = LN of Lazy<Nat> with
            override this.GetHashCode() = 0
            override this.Equals(o) =
                match o with
                | :? LazyNat as other -> 
                    let (LN(a)) = this
                    let (LN(b)) = other
                    eqLazy a b
                | _ -> false
            interface System.IComparable with
                member this.CompareTo(o) =
                    match o with
                    | :? LazyNat as other ->
                        let (LN(a)) = this
                        let (LN(b)) = other
                        match a.Force(),b.Force() with
                        | Zero, Zero   -> 0
                        | Succ _, Zero -> 1
                        | Zero, Succ _ -> -1
                        | Succ(a), Succ(b) -> compare a b
                    | _ -> failwith "bad"
    
    let (|Force|) (ln : LazyNat) =
        match ln with LN(x) -> x.Force()
    
    let rec toLazyNat x =
        if x = 0 then LN(lazy Zero)
        else LN(lazy Succ(toLazyNat(x-1)))
    
    type LazyNat with
        static member (+)(((Force x) as lx), ((Force y) as ly)) =
            match x with
            | Succ(a) -> LN(lazy Succ(a+ly))
            | Zero -> lx
    
    module NumericLiteralQ =
        let FromZero()          =  toLazyNat 0
        let FromOne()           =  toLazyNat 1
        let FromInt32(x:int)    =  toLazyNat x
    
    let rec len = function
        | LazyList.Nil -> 0Q
        | LazyList.Cons(_,t) -> LN(lazy Succ(len t))
    
    let shortList = LazyList.of_list [1..42]
    let infiniteList = LazyList.of_seq (seq {while true do yield 1})
    printfn "%A" (len shortList < 100Q)      // true
    printfn "%A" (len infiniteList < 100Q)   // false
    

    这说明为什么人们只在哈斯克尔写这些东西。

        3
  •  2
  •   Dominic Rodger    15 年前

    你可能可以通过尝试获得超过42个元素来达到你想要的效果。

        4
  •  1
  •   Zed    15 年前

    我不确定我理解你的问题,但让我们试一试。也许你在寻找小溪。我只说二郎语,不属于FP语系,所以…

    esn_stream() ->
      fun() -> esn_stream(1) end.
    
    esn_stream(N) ->
      {N*N, fun() -> esn_stream(N+2) end}.
    

    调用流总是返回下一个元素,并且应该调用一个有趣的元素来检索下一个元素。这样你就可以得到懒惰的评价。

    然后您可以定义一个比函数长的is-stream为:

    is_stream_longer_than(end_of_stream, 0) ->
      false;
    is_stream_longer_than(_, 0) ->
      true;
    is_stream_longer_than(Stream, N) ->
      {_, NextStream} = Stream(),
      is_stream_longer_than(NextStream, N-1).
    

    结果:

    1> e:is_stream_longer_than(e:esn_stream(), 42).
    true
    
    2> S0 = e:esn_stream().
    #Fun<e.0.6417874>
    
    3> {E1, S1} = S0().
    {1,#Fun<e.1.62636971>}
    4> {E2, S2} = S1().
    {9,#Fun<e.1.62636971>}
    5> {E3, S3} = S2().
    {25,#Fun<e.1.62636971>}