为什么foldr在Haskell中的无限列表上工作但是foldl不工作?

时间:2014-12-29 00:36:47

标签: haskell lazy-evaluation fold

我一直在努力理解Haskell中的foldl vs foldr vs foldl'。我理解,当foldr在其第二个参数中是惰性时,共识是使用f,因为它反映了列表的结构。当我们知道需要处理整个列表并且foldl'在其参数中是严格的时,f会更好。

我特别感兴趣的是这样的情况:

foldr (&&) False (repeat False)

返回False

可是:

foldl (&&) False (repeat False)

永远不会完成。

foldr扩展为:

False && (False && (False && .... (False && *False*)) ... )

foldl

  && (... (&& (&& *False* False) False) ...) False

明星是传递给False的基本案例fold

foldr是否能够立即终止,因为LHS只是一个False,而foldl单个False一直在右边,它不会't'检查'直到它完成处理左手边?

1 个答案:

答案 0 :(得分:7)

让我们看一下相关定义(与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

查看每个foldrfoldl必须产生结果的机会。给定[]时,它们都会立即产生结果。在(x:xs)的情况下,如果foldr立即返回而没有评估其正确的参数(这是递归调用),f也有机会产生结果。 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

(运动)为什么会这样?