Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to define foldr using map?

After I defined map using foldr a question came to my mind:

If it is possible to define map using foldr, what about the opposite?

From my point of view it is not possible, but I can't find a proper explanation. Thanks for the help!

like image 563
Josh Avatar asked May 24 '14 22:05

Josh


People also ask

How do I use a map in Haskell?

map is a function that takes two parameters: a function and a list of elements. The type signature of map is (a -> b) -> [a] -> [b] . The (a -> b) part is the function you pass to map , we will call it f . f takes one value and returns another that may be of a different type.

What is the difference between foldr and Foldl?

Difference Between foldl and foldr The difference is that foldl is tail-recursive, whereas foldr is not. With foldr and non-optimized patterns, proc is applied to the current value and the result of recursing on the rest of the list. That is, evaluation cannot complete until the entire list has been traversed.

How does foldr work in Haskell?

To recap, with foldr , the purpose of the function argument is to take the first element of the list and the results of having folded the rest of the list, and return the new value. With foldl , the function argument takes a default value, the first element of the list, and returns a new default value.

What is the type of foldr function?

The foldr function, pronounced `fold right', is also usually implemented as a curried function and has an even more sophisticated type than map. Its type is (('a * 'b) -> 'b) -> ('b -> (('a list) -> 'b)).


2 Answers

Let's start with some type signatures.

foldr :: (a -> b -> b) -> b -> [a] -> b
map :: (a -> b) -> [a] -> [b]

We can simulate map using fold because fold is a universal operator (here's a more mathematical yet quite friendly paper on this property).

I'm sure that there's some creative way of using map to simulate foldr. That can certainly be a fun exercise. But I don't think there's a straight-forward, not "crazy pointfree" solution, and in order to explain it let's forget about foldr for a moment and concentrate on a much simpler accumulation function:

sum :: [Int] -> Int

sum == foldr (+) 0, which means foldr implements sum. If we can implement foldr with map we can definitely implement sum with map. Can we do it?

I think sum's signature is a crashing blow - sum returns an Int, and map always returns a list of something. So maybe map can do the heavy-lifting, but we'll still need another function of type [a] -> a in order to get the final result. In our case, we'll need a function of type [Int] -> Int. Which is quite unfortunate, because that's exactly what we were trying to avoid in the first place.

So I guess the answer is: you can implement foldr using map - but it'll probably require using foldr :)

like image 195
Benesh Avatar answered Oct 06 '22 15:10

Benesh


The simplest way to look at it is to see that map preserves the spine of the list. If you look at the more general fmap (which is map, but not just for lists but for Functors in general), it's even a law that

fmap id = id

There are many ways to "cheat," but in the most direct interpretation of your question, folds are simply more general than maps. There is a nice trick that's used in Edward Kmett's Lens library quite a lot. Consider the Const monad, which is defined as follows:

newtype Const a b = Const { runConst :: a }

instance Functor (Const a) where fmap _ (Const a) = Const a
instance (Monoid a) => Monad (Const a) where
    return _ = Const mempty
    Const a >>= Const b = Const (a <> b)

Now you can formulate a fold in terms of the monadic map operation mapM, as long as the result type is monoidal:

fold :: Monoid m => [m] -> m
fold = runConst . mapM Const
like image 27
holzensp Avatar answered Oct 06 '22 15:10

holzensp