foldr与foldl(或foldl')的含义

时间:2008-12-21 18:53:15

标签: recursion functional-programming fold haskell

首先,我正在阅读的真实世界Haskell 表示从不使用foldl而是使用foldl'。所以我相信它。

但我对什么时候使用foldrfoldl'感到茫然。虽然我可以看到他们如何以不同的方式摆放在我面前的结构,但是当“哪个更好”时,我太愚蠢了。我想在我看来似乎并不重要,因为它们都产生相同的答案(不是吗?)。事实上,我之前使用此构造的经验来自Ruby的inject和Clojure的reduce,它们似乎没有“左”和“右”版本。 (附带问题:他们使用哪个版本?)

任何可以帮助像我这样的智能挑战的洞察力都会非常感激!

7 个答案:

答案 0 :(得分:164)

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

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

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更有空间(并且可能是时间)效率。 。

答案 1 :(得分:52)

foldr看起来像这样:

Right-fold visualization

foldl看起来像这样:

Left-fold visualization

上下文:Haskell wiki上的Fold

答案 2 :(得分:33)

他们的语义不同,所以你不能只交换foldlfoldr。一个元素从左侧向上折叠,另一个元素从右侧折叠。这样,操作员以不同的顺序应用。这对于所有非关联操作都很重要,例如减法。

Haskell.org对此主题有一个有趣的article

答案 3 :(得分:23)

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

答案 4 :(得分:18)

{99}的所有用途foldl'首选foldl的原因是它可以在大多数用途的恒定空间中运行。

使用函数sum = foldl['] (+) 0。使用foldl'时,会立即计算总和,因此将sum应用于无限列表将永远运行,并且很可能在常量空间中(如果您使用Int之类的内容s,Double s,Float s。Integer如果数字大于maxBound :: Int,则将使用多于常量空格。

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

希望有所帮助。

答案 5 :(得分:14)

顺便说一句,Ruby的inject和Clojure的reducefoldl(或foldl1,具体取决于您使用的版本)。通常,当一种语言中只有一个表单时,它是左侧的表单,包括Python的reduce,Perl的List::Util::reduce,C ++的accumulate,C#的Aggregate,Smalltalk的{{ 1}},PHP的inject:into:,Mathematica的array_reduce等,Common Lisp的Fold默认为左侧折叠,但右侧折叠有一个选项。

答案 6 :(得分:6)

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

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

例如,列表追加运算符(++)可以用foldr实现

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

,而

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

会给你一个类型错误。