了解 Haskell 中的折叠

Understanding Folds in Haskell

根据我对 Haskell 中折叠的理解,foldl (-) 0 [1..5] 通过计算 0-1-2-3-4-5 给出了 -15 的结果,而 foldr (-) 0 [1..5] 给出了结果-5 通过计算 5-4-3-2-1-0。为什么 foldl (++) "" ["a", "b", "c"]foldr (++) "" ["a", "b", "c"] 都给出 "abc" 的结果,而 foldr 的结果不是 "cba"?

在理解 foldlfoldr 之间的区别时,我是否遗漏了什么?

象征性地被视为折叠的 "translation",0-1-2-3-4-5 本身是不明确的。操作顺序必须指定。

其实无论什么算子,顺序都是

foldl (-) 0 [1..5] = ((((0 - 1) - 2) - 3) - 4) - 5    -- = -15

foldr (-) 0 [1..5] = 1 - (2 - (3 - (4 - (5 - 0))))    -- = 3

但是对于 (++),当使用 "" 代替 0 时,两种排序结果相同。

实际上 foldr (-) 0 [1..5] 等于 3,因为它是:

(1 - (2 - (3 - (4 - (5 - 0))))

这个问题的答案在foldr函数的类型中:

foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

如我们所见,(a -> b -> b) 函数将迭代元素作为第一个参数,将累加器作为第二个参数。这就是为什么 foldr (++) "" ["a", "b", "c"] 我们有:

("a" ++ ("b" ++ ("c" ++ "")))

我认为 this part from the docs 更清楚:


In the case of lists, foldr, when applied to a binary operator, a starting value (typically the right-identity of the operator), and a list, reduces the list using the binary operator, from right to left:

foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...)

. . .

In the case of lists, foldl, when applied to a binary operator, a starting value (typically the left-identity of the operator), and a list, reduces the list using the binary operator, from left to right:

foldl f z [x1, x2, ..., xn] == (...((z `f` x1) `f` x2) `f`...) `f` xn

如果您查看示例细分,串联 foldr 等同于:

"a" ++ ("b" ++ ("c" ++ ""))

而对于 foldl,它等同于:

(("" ++ "a") ++ "b") ++ "c"

对于字符串连接,这些是相同的。


但是对于减法,

1 - (2 - (3 - 0))

给出的结果不同于:

((0 - 1) - 2) - 3

我发现这是获得差异的最有指导意义的方法(因为 'left associative' 的想法对我来说是新的): -- 评论反映 :doc foldr 和 :doc foldl

*Main> foldr (-) 2 [1,4,8] -- foldr f z [a,b,c] == a f (b f(c fz))

3

*Main> 1 - (4 - ( 8 - 2) ) -- foldr 右关联

3

*Main> ((2 - 1) - 4) -8 -- foldl 左结合

-11

*Main> foldl (-) 2 [1,4,8] -- foldl f z [a,b,c] == ((z f a) f b) fc

-11