Haskell的`seq`冗余地评估参数?

时间:2018-05-19 08:29:01

标签: performance haskell

如果我正确理解了讨论here,则seq不应该对值进行两次评估,因为x `seq` x应该评估x一次。

那我为什么会有这种行为?

λ> :set +s
λ> let fib x = if x <= 1 then x else fib (x - 1) + fib (x - 2)
(0.01 secs, 102,600 bytes)
λ> fib 30
832040
(2.49 secs, 638,088,448 bytes)
λ> let x = fib 30 in x
832040
(2.47 secs, 638,088,792 bytes)
λ> let x = fib 30 in x `seq` x
832040
(4.95 secs, 1,276,067,128 bytes)

这显然是双重评估?我误解了什么吗?

编辑:正如下面的@danidiaz所说,我也评估了

λ> (\x -> x `seq` x) (fib 30)
832040
(2.51 secs, 638,087,888 bytes)
λ> let x = (fib 30) :: Int in x `seq` x
832040
(2.52 secs, 732,476,640 bytes)

现在更令人惊讶。

编辑2:我看到这个问题已被标记为先前问题的副本,该问题询问单态限制。当我遇到这个问题时,我不知道这是由于限制。因此,如果有人发现他/她在我的位置,我想这个问题的答案会有所帮助。

1 个答案:

答案 0 :(得分:15)

对于这个答案的第一部分,ghci中的:set -XNoMonomorphismRestriction。这将在后面解释。

天真地,人们会期望在Haskell中let x = 5 in (x + 1,x + 2)始终等同于(\x -> (x + 1, x + 2)) 5。但他们有不同的类型!

let x = 5 in (x + 1,x + 2) :: (Num a, Num b) => (a, b)

(\x -> (x + 1,x + 2)) 5 :: Num b => (b, b)

原因是Haskell的一个功能称为let-bound polymorphism。与lambda绑定标识符不同,绑定在let中的标识符可以在let的主体中以不同方式实例化。例如:

ghci> let f = id in (f True, f 'a')
(True,'a')

ghci> (\f -> (f True, f 'a')) id
*** ERROR ***

现在,你没有为你的fib功能提供类型签名,而推断的那个就是

fib :: (Ord a, Num a) => a -> a

适用于Num的不同实例,例如IntFloat等。

但正因为如此,当你写x `seq` x时,ghci无法确定两个x实际上是同一类型!如果他们可能不同,那么他们就无法分享。

这就是(\x -> x `seq` x) (fib 30)确实有共享的原因。因为x是lambda绑定的,所以编译器确保两个出现的值实际上是相同的。与let x = (fib 30) :: Int in x `seq` x相同,因为我们已使用显式类型删除了多态性。

还有另外一条出路。启用-XMonomorphismRestriction扩展会增加默认类型的数量,从而导致let表达式比单个预期更加单一。在这种情况下,这应该足以恢复共享。