Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you define map and filter using foldr in Haskell?

I'm doing a bit of self study on functional languages (currently using Haskell). I came across a Haskell based assignment which requires defining map and filter in terms of foldr. For the life of me I'm not fully understanding how to go about this.

For example when I define a map function like:

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] xs

I don't know why the first element of the list is always ignored. Meaning that:

map' (*2) [1,2,3,4]

results in [4,6,8] instead of [2,4,6,8]

Similarly, my filter' function:

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] xs

when run as:

filter' even [2,3,4,5,6]

results in [4,6] instead of [2,4,6]

Why would this be the case? And how SHOULD I have defined these functions to get the expected results? I'm assuming something is wrong with my lambda expressions...

like image 942
klactose Avatar asked Apr 20 '11 06:04

klactose


People also ask

How is map defined in Haskell?

The first question we need to answer is: what is a map? A map is a higher-order function that requires an array and another function. The other function is applied to the array and returns a new array, based on the application.

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]

What is the difference between Foldl and Foldr?

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.


4 Answers

I wish I could just comment, but alas, I don't have enough karma.

The other answers are all good ones, but I think the biggest confusion seems to be stemming from your use of x and xs.

If you rewrote it as

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\y ys -> (f y):ys) [] xs

you would clearly see that x is not even mentioned on the right-hand side, so there's no way that it could be in the solution.

Cheers

like image 122
tredontho Avatar answered Sep 19 '22 06:09

tredontho


For your first question, foldr already has a case for the empty list, so you need not and should not provide a case for it in your own map.

map' f = foldr (\x xs -> f x : xs) []

The same holds for filter'

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

Nothing is wrong with your lambda expressions, but there is something wrong with your definitions of filter' and map'. In the cons case (x:xs) you eat the head (x) away and then pass the tail to foldr. The foldr function can never see the first element you already ate. :)

Alse note that:

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

is equivalent (η-equivalent) to:

filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs
like image 29
Alessandro Vermeulen Avatar answered Sep 20 '22 06:09

Alessandro Vermeulen


I would define map using foldr and function composition as follows:

map :: (a -> b) -> [a] -> [b]
map f = foldr ((:).f) []

And for the case of filter:

filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr (\x xs -> if p x then x:xs else xs) []

Note that it is not necessary to pass the list itself when defining functions over lists using foldr or foldl. The problem with your solution is that you drop the head of the list and then apply the map over the list and this is why the head of the list is missing when the result is shown.

like image 45
coffeMug Avatar answered Sep 18 '22 06:09

coffeMug


In your definitions, you are doing pattern matching for x:xs, which means, when your argument is [1,2,3,4], x is bound to 1 and xs is bound to the rest of the list: [2,3,4].

What you should not do is simply throw away x: part. Then your foldr will be working on whole list.

So your definitions should look as follows:

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f xs       = foldr (\x xs -> (f x):xs) [] xs

and

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p xs        = foldr (\x xs -> if p x then x:xs else xs ) [] xs
like image 34
x13n Avatar answered Sep 18 '22 06:09

x13n