让我们看一下相关的定义(不完全与Prelude相同,但对于这个分析来说是等效的)。
(&&) :: Bool -> Bool -> Bool
True && x = x
False && _ = False
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
观察foldr
和foldl
各自产生结果的机会。
它们在给定[]
时都立即产生结果。
对于(x:xs)
情况,如果f
立即返回而不评估其右参数(递归调用),foldr
也有机会产生结果。
但是foldl
没有这个机会,因为它最外层的调用是它自己,所以foldl
只能在[]
情况下提供信息,而对于无限列表永远不会到达该情况。
在此类示例中,我发现手动计算有助于理解。回想一下Haskell的求值顺序,它从外到内进行:我们尽可能少地评估以达到最外层函数应用的可应用模式匹配。在每步中,我将用斜体表示要评估的下一个函数。 foldr
很简单:
foldr (&&) False (repeat False)
= foldr (&&) False (False : repeat False)
= False && foldr (&&) False (repeat False)
= False
foldl
揭示了问题:
foldl (&&) False (repeat False)
= foldl (&&) False (False : repeat False)
= foldl (&&) (False && False) (repeat False)
= foldl (&&) (False && False) (False : repeat False)
= foldl (&&) ((False && False) && False) (repeat False)
= foldl (&&) ((False && False) && False) (False : repeat False)
= foldl (&&) (((False && False) && False) && False) (repeat False)
等等。请注意,即使(&&)
能够通过检查任一侧来简化,我们仍然永远没有机会返回,因为我们从未到达[]
情况。
但是,(&&)
评估其参数的顺序仍然很重要(由模式匹配语义确定它首先评估左参数)。我们可以用flip
翻转参数的顺序并查看foldr
的结果:
ghci> foldr (flip (&&)) False (repeat False)
^CInterrupted
(练习) 为什么?
flip (&&)
来检查你的假设。 - luquiflip (&&)
结束,但我不确定flip
在这种情况下有什么影响。同样,如果我的假设正确,我更加好奇的是 为什么 会这样,并且为什么不能检查两种情况下的一个参数是否为False
并且在处理剩余部分之前返回False
。 - user2666425