Haskell series part 7

This is the seventh article of a series on the functional language Haskell for beginners

Haskell series part 7

Thank you for joining us for the seventh part of our Haskell series, you will find the previous article here where I explain higher order functions.

In this article we are going to cover three essentials functions in the functional programming world: map, filter and foldl.  Then we are going to learn how to compose functions.

Map

map is a very standard function in functional programming, it basically applies a function to every element of a list and returns a new list. We wrote our own version of map in the previous article using a list comprehension, but Haskell has its own version, and we do not even need to import it !

Let's check the definition:

ghci> :t map
map :: (a -> b) -> [a] -> [b]

Let's analyze it:

  • (a -> b) : This is the first parameter, it is a function which take a single parameter of type a and returns a single parameter of type b. It makes sense because most of the time we use map to "transform" data in a list.
  • [a]: This is the second parameter, it is a list of a. It's logical, our function requires a as an "input".
  • [b]: This is the return value, it is a list of b, our "output".

Let's try it out:

ghci> map (subtract 3) [1, 2, 3, 4, 5]
[-2,-1,0,1,2]
ghci> map (+10) [1, 2, 3, 4, 5]
[11,12,13,14,15]

Looks good.

Filter

filter is also a very common function in functional programming, it essentially takes a predicate (a function which returns True or False based on the value being tested) to filter out elements from a list.

Let's check the definition:

ghci> :t filter
filter :: (a -> Bool) -> [a] -> [a]

Let's analyze it:

  • (a -> Bool) : This is the first parameter, our predicate. It takes an a as a parameter and returns a Boolean. If True it will be added to the new list, else it will be ignored.
  • [a]: This is the second parameter, our list of [a].
  • [a]: This is the return value, you will note it is still a list of a this time, we do not "transform" the value, hence its type not changing.

Let's try it out:

ghci> filter (>3) [1,2,3,4,5]
[4,5]
ghci> filter odd [1,2,3,4,5]
[1,3,5]

Looks good, you will note that we do not need any parenthesis for odd because it does not have a parameter.

Foldl

foldl is also pretty famous in the functional programming world but usually known under a different pseudonym: reduce. This function acts like a map function but "reduces" the result to a summary value, or, to use Haskell terms: it "folds" the result to a summary value. In this very specific situation, it folds the list from the left side (hence foldl, foldr exists for the same purpose but from the right side).

Let's check the definition:

ghci> :t foldl
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b

Let's analyze it:

  • We discover a new typeclass called Foldable (documentation) which defines data structure that can be folded to a summary value.
  • (b -> a -> b) : This is our first parameter, it is a function that takes 2 parameters and returns a single value. b is the accumulator (or summary  value), a is an element from our Foldable data structure (typically a List, but others exists).
  • b: This is our second parameter, the accumulator.
  • t a: This is our third parameter, the Foldable data structure.
  • b: This is our return value, the accumulator.

Let's try it out:

ghci> foldl (\acc x -> acc + x) 0 [1,2,3,4,5]
15

So what happened here:

  • We are calling foldl.
  • Our first parameter is a lambda function with 2 parameters: acc is the accumulator and x is the current element of the list.
  • Our second parameter is the accumulator which we initialized to 0.
  • Our third parameter is the list of values.
  • Our result is 15, because it is the result of 0 + 1 + 2 + 3 + 4 + 5

Function composition

Now, it is pretty common to "chain" functions in programming, functional or not. Because we did not visited yet concepts like record or data, we are going to apply it again on a list of numbers. But soon enough we will be able to demonstrate Haskell features using better test data.

Let's say that we need to negate a number and then add 2 to it. Because we are familiar with map and lambdas we would write something like this:

ghci> map (\x -> negate x + 2) [1,2,3,4,5]
[1,0,-1,-2,-3]

Which is perfectly fine, but it can be finer by using the dot operator: . !

Let's try it out:

ghci> map ((+2) . negate) [1,2,3,4,5]
[1,0,-1,-2,-3]

Looks better, there is less "noise" without any backslash or arrows. But note that . is right associative. I chose negate on purpose to show that the order matter:

ghci> map (negate . (+2)) [1,2,3,4,5]
[-3,-4,-5,-6,-7]

Your results would be completely different if you would switch the order, so keep it in mind the next time you need to compose functions.

Additional notes:

If you were looking into "chaining" map and filter, let's say to filter numbers that can be divided by 3 and multiply them by 42 .I recommend doing either of the following things:

Option 1: Use the output of filter as the input to map:

ghci> map (*42) (filter (\x -> mod x 3 == 0) [1,2,3,4,5,6,7,8,9])
[126,252,378]

Option 2: Use list comprehensions, which is much more legible in my opinion:

ghci> [x * 42 | x <- [1,2,3,4,5,6,7,8,9], (mod x 3 == 0)]
[126,252,378]

Conclusion

Seven down, around three more articles to go ! That's it for the map, filter , foldl and function composition in Haskell.  In our next article we are going to discuss about I/O (Input and Output) which we saw very briefly in our first article where we wrote and compiled our first program.

PS: Part 8 can be found here.

If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.