foldr与foldl(或foldl')的含义
首先,我正在阅读的真实世界Haskell说永远不会使用foldl
而是使用foldl'
。 所以我相信它。
但是我对什么时候使用foldr
与foldl'
朦胧。 虽然我可以看到他们在我面前的工作方式不同,但我太愚蠢地不明白什么时候“哪个更好”。 我想在我看来,应该使用哪一个并不重要,因为它们都会产生相同的答案(不是吗?)。 事实上,我以前使用这个构造的经验是来自Ruby的inject
和Clojure的reduce
,它们似乎没有“左”和“右”版本。 (侧面问题:他们使用哪个版本?)
任何能够帮助像我这样智慧挑战的洞察力都将受到高度赞赏!
ys = [y1,y2,...,yk]
看起来像foldr fx ys
的recursion
f y1 (f y2 (... (f yk x) ...))
而foldl fx ys
的recursion看起来像
f (... (f (fx y1) y2) ...) yk
这里的一个重要区别是,如果fxy
的结果只能使用x
的值计算,那么foldr
不需要检查整个列表。 例如
foldr (&&) False (repeat False)
返回False
而
foldl (&&) False (repeat False)
永不终止。 (注意: repeat False
会创build一个无限列表,其中每个元素都是False
。)
另一方面, foldl'
是尾recursion和严格的。 如果你知道你不得不遍历整个列表(例如,将列表中的数字相加),那么foldl'
比foldr
更具有空间效率(可能是时间效率)。
foldr
看起来像这样:
foldl
看起来像这样:
上下文:在Haskell wiki上折叠
他们的语义不同,所以你不能只交换foldl
和foldr
。 一个从左边起,另一个从右侧折叠起来。 这样,操作员按照不同的顺序进行申请。 这对所有非关联操作都很重要,如减法。
Haskell.org有一个关于这个主题的有趣的文章 。
不久之后,当第二个参数中的累加器函数懒惰时, foldr
会更好。 阅读更多Haskell wiki的堆栈溢出 (双关意图)。
foldl'
为所有用途的99%优选foldl
的原因是它可以运行在恒定的空间用于大多数用途。
取sum = foldl['] (+) 0
的函数。 当使用foldl'
,立即计算总和,所以将sum
加到一个无限列表中将会永远运行,并且最有可能在恒定的空间中(如果你正在使用Int
s, Double
s, Float
s, Integer
s如果数字大于maxBound :: Int
则使用多于常量的空格)。
通过foldl
,可以build立一个thunk(就像怎样获得答案的配方,可以在以后评估,而不是存储答案)。 这些thunk可以占用大量的空间,在这种情况下,评估expression式要比存储thunk好得多(导致堆栈溢出…并导致你…哦,不要紧)
希望有所帮助。
顺便说一句,Ruby的inject
和Clojure的reduce
是foldl
(或foldl1
,取决于你使用的版本)。 通常,当一种语言只有一种forms的时候,它是一个左边的折叠,包括Python的reduce
,Perl的List::Util::reduce
,C ++的accumulate
,C#的Aggregate
,Smalltalk的inject:into:
,PHP的array_reduce
,Mathematica的Fold
等等。 。Common Lisp reduce
默认值reduce
到左边,但是有一个选项可以正确折叠。
Konrad指出,他们的语义是不同的。 他们甚至没有相同的types:
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 (:))
会给你一个types错误。