Haskell懒惰评估和重用

时间:2010-12-26 01:43:44

标签: haskell list-comprehension lazy-evaluation

我知道如果我要在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功能正常,每次结果都是相同的,如果我在其他地方再次调用square,它会重新评估它已经计算好的答案吗?如果我在调用前一行之后重新使用正方形,是否会重新计算前4行值?

print $ take 5 squares

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

4 个答案:

答案 0 :(得分:18)

在这种情况下,不会重新计算,因为列表实际上已构建,并且调用后方块列表仍然存在。但是,Haskell函数一般不会被记忆。这只适用于这样的情况,你没有显式调用函数,只是探索(in)有限列表。

答案 1 :(得分:12)

此值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值专门化为该类型并共享结果值。)当然,如果{ {1}}用于多种不同类型,例如squaressquares :: [Double],将重新计算。

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

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:2)

为什么不使用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])

单独拿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是类型。)