我一直在努力理解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'检查'直到它完成处理左手边?
答案 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
查看每个foldr
和foldl
必须产生结果的机会。给定[]
时,它们都会立即产生结果。在(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
(运动)为什么会这样?