为什么你可以用foldl反转列表,而不是在Haskell中使用foldr

时间:2014-09-24 12:49:16

标签: haskell fold

为什么可以使用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

6 个答案:

答案 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"?这就是你如何分别找出sf的内容。

如果您考虑发生了什么,您会发现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 []

通过实例化foldlfoldr常规转换。

仅适用于数学社区。执行cabal install newtype并导入Data.MonoidData.FoldableControl.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,我们告诉foldMapDual幺半群中工作。

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"