了解 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"
?
在理解 foldl
和 foldr
之间的区别时,我是否遗漏了什么?
象征性地被视为折叠的 "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 f
z))
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) f
c
-11
根据我对 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"
?
在理解 foldl
和 foldr
之间的区别时,我是否遗漏了什么?
象征性地被视为折叠的 "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 f
z))
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) f
c
-11