【问题标题】:Haskell Lazy Evaluation and ReuseHaskell 惰性求值和重用
【发布时间】:2023-04-05 19:36:01
【问题描述】:

我知道如果我要在 Haskell 中计算一个正方形列表,我可以这样做:

squares = [ x ** 2 | x <- [1 ..] ]

然后当我这样调用方格时:

print $ take 4 squares

它会打印出 [1.0, 4.0, 9.0, 16.0]。这被评估为 [ 1 ** 2, 2 ** 2, 3 ** 2, 4 ** 2 ]。现在由于 Haskell 是功能性的并且每次结果都是相同的,如果我要在其他地方再次调用 squares,它会重新评估它已经计算的答案吗?如果我在调用上一行之后重新使用正方形,它会重新计算前 4 个值吗?

print $ take 5 squares

它会评估 [1.0, 4.0, 9.0, 16.0, 5 ** 2] 吗?

【问题讨论】:

    标签: haskell list-comprehension lazy-evaluation


    【解决方案1】:

    为什么不使用 ghci 来测试(如果 ghc 是你的编译器):

    Prelude> let squares = [ x ** 2 | x <- [1 ..] ] :: [Float]
    Prelude> :print squares
    squares = (_t6::[Float])
    

    所以所有 ghci 现在都知道你有一个列表。

    Prelude> print $ take 4 squares
    [1.0,4.0,9.0,16.0]
    Prelude> :print squares
    squares = 1.0 : 4.0 : 9.0 : 16.0 : (_t7::[Float])
    

    知道它知道你的列表以东西开头,因为它必须评估才能打印它。

    Prelude> let z = take 5 squares
    Prelude> :print z
    z = (_t8::[Float])
    

    take 5 本身不会评估任何东西

    Prelude> length z --Evaluate the skeleton
    5
    Prelude> :print z
    z = [1.0,4.0,9.0,16.0,(_t9::Float)]
    

    延长时间会导致take 顺其自然,我们认为您是对的。您也可以通过省略正方形上的类型定义来测试其多态时发生的情况。如果您不想使用 ghci,另一个好技巧是在您的代码中使用 undefined(您的程序在尝试评估 _|_ 时恰好崩溃,undefined 是其中的一种。)

    【讨论】:

      【解决方案2】:

      在这种情况下,它不会被重新计算,因为列表实际上是建立的,并且正方形列表在调用后继续存在。然而,Haskell 函数一般不会被记忆。这仅适用于您没有显式调用函数,只是探索(内)有限列表的情况。

      【讨论】:

      • 你有没有机会给我一个不会被记忆的例子?我认为 squares 是一个返回无限列表的函数。有什么我可以传递给 ghc 以查看 Haskell 在幕后所做的事情吗?
      • 不,squares 不是函数;这是一个清单。数据结构在 Haskell 中是自动记忆的,而不是函数。你仍然可以记忆函数,但你必须明确地这样做。如果您对 squares 不是函数感到困惑,您可能会喜欢博客文章 "Everything is a function" in Haskell?
      【解决方案3】:

      这个值squares 可能是多态的:

      Prelude> :t [ x ** 2 | x <- [1 ..] ]
      [ x ** 2 | x <- [1 ..] ] :: (Floating t, Enum t) => [t]
      

      AFAIK,是否会重新计算(在 GHC 中)取决于顶级值 squares 是否被赋予多态类型。我相信 GHC 不会对涉及类型类(从类型到值的函数)的多态值进行任何记忆,就像它不会对普通函数(从值到值的函数)进行任何记忆一样。

      这意味着如果你定义squares

      squares :: [Double]
      squares = [ x ** 2 | x <- [1 ..] ]
      

      那么squares只会被计算一次,而如果你定义它

      squares :: (Floating t, Enum t) => [t]
      squares = [ x ** 2 | x <- [1 ..] ]
      

      那么它很可能会在每次使用时计算,即使它以相同的类型重复使用。 (不过,我还没有对此进行测试,如果 GHC 看到 squares :: [Double] 的多种用途,它可能会将 squares 值专门化为该类型并共享结果值。)当然,如果使用 squares在几种不同的类型,例如squares :: [Double]squares :: [Float],它将被重新计算。

      如果您没有为squares 提供任何类型签名,那么monomorphism restriction 将适用于它,除非您将其禁用。结果将是squares 被分配了一个单态类型,从程序的其余部分推断(或根据默认规则)。单态限制的目的正是为了确保看起来像它们只会被评估一次的值,比如你的squares,实际上只会被评估一次。

      【讨论】:

        【解决方案4】:

        为了澄清已经给出的答案,这是一个 Haskell 函数:

        thisManySquares n = map (^2) [1..n]
        

        因此,如果您将“thisManySquares 4”的调用替换为您的take 4 squares,那么是的,它会重复调用该函数。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-11-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多