为什么可以使用foldl反转列表?
reverse' :: [a] -> [a]
reverse' xs = foldl (\acc x-> x : acc) [] xs
但是这个给了我一个编译错误。
reverse' :: [a] -> [a]
reverse' xs = foldr (\acc x-> x : acc) [] xs
错误
Couldn't match expected type `a' with actual type `[a]'
`a' is a rigid type variable bound by
the type signature for reverse' :: [a] -> [a] at foldl.hs:33:13
Relevant bindings include
x :: [a] (bound at foldl.hs:34:27)
acc :: [a] (bound at foldl.hs:34:23)
xs :: [a] (bound at foldl.hs:34:10)
reverse' :: [a] -> [a] (bound at foldl.hs:34:1)
In the first argument of `(:)', namely `x'
In the expression: x : acc
答案 0 :(得分:23)
每个foldl
都是foldr
。
让我们记住这些定义。
foldr :: (a -> s -> s) -> s -> [a] -> s
foldr f s [] = s
foldr f s (a : as) = f a (foldr f s as)
这是列表的标准问题一步迭代器。我曾经让我的学生敲打桌子并唱歌#34;你对空名单怎么办?您如何处理a : as
"?这就是你如何分别找出s
和f
的内容。
如果您考虑发生了什么,您会发现foldr
有效地计算了f a
函数的大量组合,然后将该合成应用于s
。
foldr f s [1, 2, 3]
= f 1 . f 2 . f 3 . id $ s
现在,让我们查看foldl
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t [] = t
foldl g t (a : as) = foldl g (g t a) as
这也是对列表的一步迭代,但是随着累加器的改变而我们去了。让我们将其移动到最后,以便列表参数左侧的所有内容保持不变。
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] t = t
flip (foldl g) (a : as) t = flip (foldl g) as (g t a)
现在我们可以看到一步迭代,如果我们将=
向左移动一个位置。
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> flip (foldl g) as (g t a)
在每种情况下,我们计算如果我们知道使用\ t ->
抽象的累加器,我们会怎么做。对于[]
,我们会返回t
。对于a : as
,我们将使用g t a
作为累加器处理尾部。
但现在我们可以将flip (foldl g)
转换为foldr
。摘要递归调用。
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> s (g t a)
where s = flip (foldl g) as
现在,我们可以将其转换为foldr
,其中s
类型t -> t
已实例化。
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)
所以s
说" as
会对累加器做什么"我们回复\ t -> s (g t a)
,这是" a : as
对累加器"的作用。翻转。
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t))
ETA-扩大。
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)) t as
减少flip
。
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t) as t
所以我们计算"如果我们知道累加器"我们会做什么,然后我们将它提供给初始累加器。
对于高尔夫球而言,这有点适中。我们可以摆脱\ t ->
。
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> s . (`g` a)) id as t
现在让我使用>>>
中的Control.Arrow
反转该合成。
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> (`g` a) >>> s) id as t
也就是说,foldl
计算一个大的反向组合。因此,例如,给定[1,2,3]
,我们得到
foldr (\ a s -> (`g` a) >>> s) id [1,2,3] t
= ((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
"管道"从左边开始提供它的参数,所以我们得到
((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
= ((`g` 2) >>> (`g` 3) >>> id) (g t 1)
= ((`g` 3) >>> id) (g (g t 1) 2)
= id (g (g (g t 1) 2) 3)
= g (g (g t 1) 2) 3
如果您选择g = flip (:)
和t = []
,则
flip (:) (flip (:) (flip (:) [] 1) 2) 3
= flip (:) (flip (:) (1 : []) 2) 3
= flip (:) (2 : 1 : []) 3
= 3 : 2 : 1 : []
= [3, 2, 1]
即,
reverse as = foldr (\ a s -> (a :) >>> s) id as []
通过实例化foldl
到foldr
的常规转换。
仅适用于数学社区。 strong>执行cabal install newtype
并导入Data.Monoid
,Data.Foldable
和Control.Newtype
。添加悲惨的实例:
instance Newtype (Dual o) o where
pack = Dual
unpack = getDual
请注意,一方面,我们可以foldMap
foldr
foldMap :: Monoid x => (a -> x) -> [a] -> x
foldMap f = foldr (mappend . f) mempty
但反之亦然
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f = flip (ala' Endo foldMap f)
以便foldr
积累在组成内功能的幺半群中,但现在要获得foldl
,我们告诉foldMap
在Dual
幺半群中工作。
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl g = flip (ala' Endo (ala' Dual foldMap) (flip g))
mappend
的{{1}}是什么?模数包装,它恰好是反向组合Dual (Endo b)
。
答案 1 :(得分:14)
首先,类型签名不排队:
foldl :: (o -> i -> o) -> o -> [i] -> o
foldr :: (i -> o -> o) -> o -> [i] -> o
所以,如果你交换你的参数名称:
reverse' xs = foldr (\ x acc -> x : acc) [] xs
现在它编译。它不会工作,但它现在可以编译。
事情是foldl
,从左到右(即向后)工作,而foldr
从右到左(即向前)工作。这就是为什么foldl
允许你反转列表的原因;它以相反的顺序交给你。
说了这么多,你可以做
reverse' xs = foldr (\ x acc -> acc ++ [x]) [] xs
然而,真的很慢。 (二次复杂度而不是线性复杂度。)
答案 2 :(得分:7)
你可以使用foldr
有效地反转列表(好吧,GHC 7.9中的大部分时间 - 它依赖于一些编译器优化),但它有点奇怪:
reverse xs = foldr (\x k -> \acc -> k (x:acc)) id xs []
我写了一个关于它是如何工作的解释on the Haskell Wiki。
答案 3 :(得分:5)
foldr
基本上以规范的方式解构列表:foldr f initial
与具有模式的函数相同:(这基本上是foldr的定义 )功能
ff [] = initial
ff (x:xs) = f x $ ff xs
即。它逐个取消元素并将它们提供给f
。好吧,如果所有f
确实让他们再次回到原点,那么你就得到了原来的名单! (另一种说法是:foldr (:) [] ≡ id
。
foldl
以相反的顺序“解构”列表,所以如果你收回元素,你会得到反向列表。要使用foldr
获得相同的结果,您需要附加到“错误”结尾 - 无论是在MathematicalOrchid显示,使用++
效率低下,还是使用差异列表:
reverse'' :: [a] -> [a]
reverse'' l = dl2list $ foldr (\x accDL -> accDL ++. (x:)) empty l
type DList a = [a]->[a]
(++.) :: DList a -> DList a -> DList a
(++.) = (.)
emptyDL :: DList a
emptyDL = id
dl2list :: DLList a -> [a]
dl2list = ($[])
可以紧凑地写成
reverse''' l = foldr (flip(.) . (:)) id l []
答案 4 :(得分:4)
这是foldl op acc
对包含6个元素的列表所做的事情:
(((((acc `op` x1) `op` x2) `op` x3) `op` x4) `op` x5 ) `op` x6
而foldr op acc
执行此操作:
x1 `op` (x2 `op` (x3 `op` (x4 `op` (x5 `op` (x6 `op` acc)))))
当您看到这一点时,很明显,如果您希望foldl
反转列表,op
应该是"将右操作数粘贴到左操作数的开头&# 34;运营商。只有(:)
的论点被颠倒过来,即
reverse' = foldl (flip (:)) []
(这与您的版本相同,但使用内置函数)。
如果希望foldr
反转列表,则需要"将左操作数粘贴到右操作数的末尾"运营商。我不知道这样做的内置函数;如果您愿意,可以将其写为flip (++) . return
。
reverse'' = foldr (flip (++) . return) []
或者如果你喜欢自己写的
reverse'' = foldr (\x acc -> acc ++ [x]) []
但这会很慢。
答案 5 :(得分:2)
这些答案的一些细微但重要的概括是你可以用foldl
实现foldr
,我认为这是一种更清楚的方式来解释其中发生的事情:
myMap :: (a -> b) -> [a] -> [b]
myMap f = foldr step []
where step a bs = f a : bs
-- To fold from the left, we:
--
-- 1. Map each list element to an *endomorphism* (a function from one
-- type to itself; in this case, the type is `b`);
--
-- 2. Take the "flipped" (left-to-right) composition of these
-- functions;
--
-- 3. Apply the resulting function to the `z` argument.
--
myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z as = foldr (flip (.)) id (toEndos f as) z
where
toEndos :: (b -> a -> b) -> [a] -> [b -> b]
toEndos f = myMap (flip f)
myReverse :: [a] -> [a]
myReverse = myfoldl (flip (:)) []
有关这些想法的更多解释,我建议阅读Tom Ellis'"What is foldr
made of?"和Brent Yorgey "foldr
is made of monoids"。