这是一个非常非常简单的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
应该。
但是,当您查看折叠实际上正在做什么时,反转x
和acc
变量是完全合理的。
鉴于列表[1,2,3],identityL依次是[x]
到现有的acc
:
((([] ++ [1]) ++ [2]) ++ [3])
而identityR依次是acc
到起始[x]
:
([1] ++ ([2] ++ ([3] ++ [])))
答案 0 :(得分:7)
foldr
和foldl
都将列表传递给函数,参数顺序相同。
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
定义foldr
和foldl
的方式,foldr
和foldl
始终给出相同的结果,假设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
对于非关联操作,这是无关紧要的,因为foldr
和foldl
通常不会给出相同的结果。对于可交换操作,这是无关紧要的,因为无论参数的顺序如何,foldr
和foldl
都会给出相同的结果。这只对一般的幺半群有用。
或者换句话说,您可以将foldr
和foldl
视为将自由幺半群(a.k.a列表)转换为您选择的另一个幺半群的工具。
答案 1 :(得分:2)
如果您认为foldr
替换了列表中的(:)
和[]
,则传递给该函数的参数顺序非常有意义:
xs = x1 : x2 : x3 : ... : []
foldr f a xs = x1 `f` x2 `f` x3 `f` ... `f` a