为什么foldr反转foldl的参数?

时间:2015-01-13 01:19:46

标签: haskell fold

这是一个非常非常简单的foldl函数:它接受一个列表,并返回相同的列表:

identityL :: (Integral a) => [a] -> [a]
identityL xs = foldl (\ acc x -> acc ++ [x]) [] xs

我尝试用foldr做同样的事情,认为我所要做的就是将foldl改为foldr并翻转" acc"和" [x]"围绕" ++"。但显然,您还需要翻转参数:

identityR :: (Integral a) => [a] -> [a]
identityR xs = foldr (\ x acc -> [x] ++ acc) [] xs

这似乎非常违反直觉,对于一个似乎用于对累加器线性处理列表的函数。我可能不正确地接近这个概念;是否有一个简单的折叠模型证明了翻转参数的奇怪性?

编辑:这很傻;如所指出的,参数排序相互抵消。以下代码有效:

identityR :: (Integral a) => [a] -> [a]
identityR xs = foldr (\ acc x -> [acc] ++ x) [] xs

我之前看过这个,但它让我感到困扰,因为acc应该是一个列表,不需要列表封装,而x应该。

但是,当您查看折叠实际上正在做什么时,反转xacc变量是完全合理的。

鉴于列表[1,2,3],identityL依次是[x]到现有的acc

((([] ++ [1]) ++ [2]) ++ [3])

而identityR依次是acc到起始[x]

([1] ++ ([2] ++ ([3] ++ [])))

2 个答案:

答案 0 :(得分:7)

foldrfoldl都将列表传递给函数,参数顺序相同。

foldr (+) 0 [1, 2, 3] = 1 + (2 + (3 + 0))
foldl (+) 0 [1, 2, 3] = ((0 + 1) + 2) + 3

这就是它的全部内容。

> foldr (\x y -> "(" ++ show x ++ " + " ++ y ++ ")") "0" [1, 2, 3]
"(1 + (2 + (3 + 0)))"
> foldl (\x y -> "(" ++ x ++ " + " ++ show y ++ ")") "0" [1, 2, 3]
"(((0 + 1) + 2) + 3)"

数学

假设你有一个幺半群,M:

identity :: M
op :: M -> M -> M

-- Monoid laws:
-- x `op` (y `op` z) = (x `op` y) `op` z
-- x `op` identity = x
-- identity `op` x = x

定义foldrfoldl的方式,foldrfoldl始终给出相同的结果,假设op为总数。他们只是用不同的方式将同一个表达式括起来。

foldr op identity == foldl op identity

例如,

concat :: [[a]] -> [a]
-- both definitions give same result
concat = foldr (++) []
concat = foldl (++) []

sum :: Num a => [a] -> a
-- both definitions give same result
sum = foldr (+) 0
sum = foldl (+) 0

product :: Num a => [a] -> a
-- both definitions give same result
product = foldr (*) 1
product = foldl (*) 1

对于非关联操作,这是无关紧要的,因为foldrfoldl通常不会给出相同的结果。对于可交换操作,这是无关紧要的,因为无论参数的顺序如何,foldrfoldl都会给出相同的结果。这只对一般的幺半群有用。

或者换句话说,您可以将foldrfoldl视为将自由幺半群(a.k.a列表)转换为您选择的另一个幺半群的工具。

答案 1 :(得分:2)

如果您认为foldr替换了列表中的(:)[],则传递给该函数的参数顺序非常有意义:

xs =           x1  :  x2  :  x3  :  ...  :  []
foldr f a xs = x1 `f` x2 `f` x3 `f` ... `f` a