164

首先,我正在阅读的Real World Haskellfoldl说永远不要使用,而是使用foldl'. 所以我相信它。

但我不清楚何时使用foldrvs. foldl'. 虽然我可以看到它们在我面前展示的不同工作方式的结构,但我太愚蠢了,无法理解什么时候“哪个更好”。我想在我看来,使用哪个并不重要,因为它们都产生相同的答案(不是吗?)。事实上,我之前对这种构造的经验来自 Rubyinject和 Clojure 的reduce,它们似乎没有“左”和“右”版本。(附带问题:他们使用哪个版本?)

任何可以帮助像我这样聪明的人的见解将不胜感激!

4

7 回答 7

180

foldr f x yswhere的递归ys = [y1,y2,...,yk]看起来像

f y1 (f y2 (... (f yk x) ...))

而 for 的递归foldl f x ys看起来像

f (... (f (f x y1) y2) ...) yk

这里的一个重要区别是,如果 的结果f x y可以仅使用 的值来计算x,则foldr不需要检查整个列表。例如

foldr (&&) False (repeat False)

返回False

foldl (&&) False (repeat False)

永不终止。(注意:repeat False创建一个无限列表,其中每个元素都是False。)

另一方面,foldl'是尾递归和严格的。如果您知道无论如何都必须遍历整个列表(例如,将列表中的数字相加),那么foldl'空间(可能还有时间)效率比foldr.

于 2008-12-21T20:41:41.593 回答
58

foldr看起来像这样:

右折叠可视化

foldl看起来像这样:

左折叠可视化

背景:折叠在 Haskell wiki 上

于 2010-01-30T22:39:59.763 回答
34

它们的语义不同,因此您不能只交换foldlfoldr。一个从左侧折叠元素,另一个从右侧折叠。这样,操作符就会以不同的顺序被应用。这对所有非关联操作都很重要,例如减法。

Haskell.org有一篇关于这个主题的有趣文章

于 2008-12-21T19:01:02.013 回答
23

简而言之,foldr当累加器函数在其第二个参数上是惰性的时会更好。在 Haskell wiki 的Stack Overflow 上阅读更多内容(双关语)。

于 2008-12-21T19:01:26.710 回答
19

foldl'对于 99% 的用途来说,首选的原因是foldl它可以在大多数用途中在恒定空间中运行。

取函数sum = foldl['] (+) 0。使用时foldl',会立即计算总和,因此应用于sum无限列表将永远运行,并且很可能在恒定空间中(如果您使用 s、s、s.s 之类的Int东西Double,如果数字变得大于)。FloatIntegermaxBound :: Int

有了foldl,就会建立一个 thunk (就像如何获得答案的配方,可以稍后评估,而不是存储答案)。这些 thunk 会占用大量空间,在这种情况下,评估表达式要比存储 thunk 好得多(导致堆栈溢出……并导致……哦,没关系)

希望有帮助。

于 2008-12-28T13:08:08.023 回答
14

顺便说一句,Rubyinject和 Clojurereducefoldl(或者foldl1,取决于您使用的版本)。通常,当一种语言只有一种形式时,它是左折叠,包括 Python 的reduce、Perl 的List::Util::reduce、C++ 的accumulate、C# 的Aggregate、Smalltalk 的inject:into:、PHP 的array_reduce、Mathematica 的Fold等。Common Lispreduce默认为左折叠,但也有右折叠的选项。

于 2009-05-17T17:21:32.483 回答
8

正如Konrad所指出的,它们的语义是不同的。他们甚至没有相同的类型:

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

例如,列表附加运算符 (++) 可以用foldras来实现

(++) = flip (foldr (:))

尽管

(++) = flip (foldl (:))

会给你一个类型错误。

于 2009-05-17T17:44:47.420 回答