为什么 foldr 会反转 foldl 的参数?

Why does foldr invert foldl's parameters?

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

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 连续 ++'ing 一个 [x] 到现有的 acc:

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

而 identityR 连续 ++'ing 一个 acc 到起始 [x]:

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

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 列表)转换为您选择的另一个幺半群的工具。

如果您将 foldr 视为替换列表中的 (:)[],那么传递给函数的参数顺序就很合理了:

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