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 typea
and returns a single parameter of typeb
. It makes sense because most of the time we usemap
to "transform" data in a list.[a]
: This is the second parameter, it is a list ofa
. It's logical, our function requiresa
as an "input".[b]
: This is the return value, it is a list ofb
, 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 ana
as a parameter and returns a Boolean. IfTrue
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 ofa
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 andx
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.