Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can you reverse list with foldl, but not with foldr in Haskell

Tags:

haskell

fold

Why can you reverse a list with the foldl?

reverse' :: [a] -> [a]
reverse' xs = foldl (\acc x-> x : acc) [] xs

But this one gives me a compile error.

reverse' :: [a] -> [a]
reverse' xs = foldr (\acc x-> x : acc) [] xs

Error

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
like image 230
Rene Avatar asked Sep 24 '14 12:09

Rene


People also ask

How do you reverse a list in Haskell?

Haskell is a functional programming language where we can leverage recursion to reverse a list. This can be done by adding the first element ( x) of the list at the end of the returning list, and calling the recursive function with the list ( x s xs xs) that does not contain x.

What is Foldl and foldr in Haskell?

foldl and foldr both act as reducers on lists, using proc to "fold" each item of the list in turn into the initial value init . The signature of proc is important. More specifically, the type signature of foldl / foldr with one lst argument is (X Y -Y) Y [List-of-X] -Y.

Can Foldl return a list?

However, a fold doesn't have to return a single value. It can return a collection, such as another list, as well. The fact that folds are higher-order functions are very important, because the reason why have higher order functions at all is to take a common programming pattern and encapsulate it in a function.

What is foldr function in Haskell?

From HaskellWiki. The foldr function applies a function against an accumulator and each value of a Foldable structure from right to left, folding it to a single value. foldr is a method of the Foldable typeclass: foldr (++) [] [[0, 1], [2, 3], [4, 5]] -- returns [0, 1, 2, 3, 4, 5]


3 Answers

For a start, the type signatures don't line up:

foldl :: (o -> i -> o) -> o -> [i] -> o
foldr :: (i -> o -> o) -> o -> [i] -> o

So if you swap your argument names:

reverse' xs = foldr (\ x acc -> x : acc) [] xs

Now it compiles. It won't work, but it compiles now.

The thing is, foldl, works from left to right (i.e., backwards), whereas foldr works right to left (i.e., forwards). And that's kind of why foldl lets you reverse a list; it hands you stuff in reverse order.

Having said all that, you can do

reverse' xs = foldr (\ x acc -> acc ++ [x]) [] xs

It'll be really slow, however. (Quadratic complexity rather than linear complexity.)

like image 27
MathematicalOrchid Avatar answered Nov 04 '22 11:11

MathematicalOrchid


Every foldl is a foldr.

Let's remember the definitions.

foldr :: (a -> s -> s) -> s -> [a] -> s
foldr f s []       = s
foldr f s (a : as) = f a (foldr f s as)

That's the standard issue one-step iterator for lists. I used to get my students to bang on the tables and chant "What do you do with the empty list? What do you do with a : as"? And that's how you figure out what s and f are, respectively.

If you think about what's happening, you see that foldr effectively computes a big composition of f a functions, then applies that composition to s.

foldr f s [1, 2, 3]
= f 1 . f 2 . f 3 . id $ s

Now, let's check out foldl

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t []       = t
foldl g t (a : as) = foldl g (g t a) as

That's also a one-step iteration over a list, but with an accumulator which changes as we go. Let's move it last, so that everything to the left of the list argument stays the same.

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)

Now we can see the one-step iteration if we move the = one place leftward.

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)

In each case, we compute what we would do if we knew the accumulator, abstracted with \ t ->. For [], we would return t. For a : as, we would process the tail with g t a as the accumulator.

But now we can transform flip (foldl g) into a foldr. Abstract out the recursive call.

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

And now we're good to turn it into a foldr where type s is instantiated with t -> t.

flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)

So s says "what as would do with the accumulator" and we give back \ t -> s (g t a) which is "what a : as does with the accumulator". Flip back.

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t))

Eta-expand.

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)) t as

Reduce the flip.

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t) as t

So we compute "what we'd do if we knew the accumulator", and then we feed it the initial accumulator.

It's moderately instructive to golf that down a little. We can get rid of \ t ->.

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> s . (`g` a)) id as t

Now let me reverse that composition using >>> from Control.Arrow.

foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> (`g` a) >>> s) id as t

That is, foldl computes a big reverse composition. So, for example, given [1,2,3], we get

foldr (\ a s -> (`g` a) >>> s) id [1,2,3] t
= ((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t

where the "pipeline" feeds its argument in from the left, so we get

((`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

and if you take g = flip (:) and t = [] you get

flip (:) (flip (:) (flip (:) [] 1) 2) 3
= flip (:) (flip (:) (1 : []) 2) 3
= flip (:) (2 : 1 : []) 3
= 3 : 2 : 1 : []
= [3, 2, 1]

That is,

reverse as = foldr (\ a s -> (a :) >>> s) id as []

by instantiating the general transformation of foldl to foldr.

For mathochists only. Do cabal install newtype and import Data.Monoid, Data.Foldable and Control.Newtype. Add the tragically missing instance:

instance Newtype (Dual o) o where
  pack = Dual
  unpack = getDual

Observe that, on the one hand, we can implement foldMap by foldr

foldMap :: Monoid x => (a -> x) -> [a] -> x
foldMap f = foldr (mappend . f) mempty

but also vice versa

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f = flip (ala' Endo foldMap f)

so that foldr accumulates in the monoid of composing endofunctions, but now to get foldl, we tell foldMap to work in the Dual monoid.

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl g = flip (ala' Endo (ala' Dual foldMap) (flip g))

What is mappend for Dual (Endo b)? Modulo wrapping, it's exactly the reverse composition, >>>.

like image 143
pigworker Avatar answered Nov 04 '22 09:11

pigworker


You can use foldr to reverse a list efficiently (well, most of the time in GHC 7.9—it relies on some compiler optimizations), but it's a little weird:

reverse xs = foldr (\x k -> \acc -> k (x:acc)) id xs []

I wrote an explanation of how this works on the Haskell Wiki.

like image 9
dfeuer Avatar answered Nov 04 '22 09:11

dfeuer