Writing maps and filters in Haskell can feel daunting. With Haskell being a strictly functional programming language, the process of thinking and implementing ideas and logic can feel a little strange — especially if you are coming in from an object-oriented background.
In this article, we are going to go over what maps and filters are, and how to write them 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.
Mapping, in programming, can be considered as a process of transforming a set of data into something else based on what that something else defines.
A higher-order function is a function that operates another function. In Haskell, and a lot of other programming languages, mapping is a process of applying the map higher-order function on an array to transform it.
This brings us to the second question: why do maps matter?
When you’ve got a collection of things that you need to transform into something else, the process of doing each transformation individually can be time-consuming and repetitive. The process of mapping automates this process and condenses it down to a repeatable pattern that gets applied in a function and predictable manner.
So how do you write a map in Haskell?
When it comes to writing maps, Haskell takes a function and a list, then applies that function to every element in the list to produce a new list. Here is the syntax example of how it works:
map :: (a -> b) -> [a] -> [b]
Looks a bit confusing? Let’s take it one part at a time. The line starts with a function
(a -> b), followed by a list
[a] that this function is going to apply to.
[b] is the new list, after it has been transformed by the function.
(a -> b) is an exact copy transformation from a to b, which means that the mapped array of
[b] is going to be exactly the same as
If we wanted our map to produce something else, we would transform our function
(a -> b) to match our intention.
So what does this look like? Here is an example map in Haskell:
module MapCube where mapcube a = map (^3) a
mapcube takes in the parameter of
a , where it is then used by the
map function against the function declared. So when we run our module, we will get our returned array as cubed results.
:l MapCube mapcube [1,2,3,4]
return [1,8,27,64] as the new mapped array. This is because the function
(^3) is applied to each value in the array.
Let’s take a look at another example of a Haskell map.
module MapTax where maptax price = map (*1.15) price
In this example, the tax rate is 15% on top of the given value. So when we run it:
:l MapTax maptax [12.45, 13.00, 45.65]
maptax [12.45, 13.00, 45.65] will
return [14.32, 14.95, 52.50] because the function (*1.15) is applied to each individual array item that got passed in.
This is the general syntax for writing maps in Haskell. You can abstract the function out into another function like this:
module MapTax where taxrate n = n * 1.15 maptax price = map taxrate price
Writing it like this makes it easier to read, especially if your mapping conditions start to get longer and complicated. Based on the above, we know exactly what
maptax is trying to achieve and how it’s going to achieve it.
It’s easy to get lost in our own code, which makes it even more important to name our functions in the most descriptive way possible. This allows the code to be naturally self-documenting and highly informative for the developer that comes after you.
Now that we understand how maps work in Haskell, let’s move onto filters. Before we start, we need to understand what a filter is in a programming context.
The point of a filter in programming is to take a section of code that matches against a certain criteria and return it. Unlike map, where the map function is applied to all items in the array and returned accordingly, filters in Haskell work by observing all the values it is given and deciding if it qualifies to be a returned value or not.
If the parameters of the qualification are not met, the value is not returned. Unlike mapping, when you filter something, the returned value cannot be reversed. This is because it omits the unqualified values from the original list, meaning that the data is lost once the filter has completed its iteration over the data set.
Like maps in Haskell, a filter is also another type of higher-order function that you can employ for your purposes.
Here is what a filter looks like according to Haskell’s documentation.
filter :: (a -> Bool) -> [a] -> [a]
Notice here that the filtered value doesn’t create a new array. In map, we see that
[a] transforms into
[b] via the syntax
[a] -> [b]. However, in a filter function,
[a] -> [a], which means that it replaces the original dataset completely.
So what does writing a filter in Haskell look like? Here is an example with an abstracted function:
module FilterExample where isEven n = n
mod2 == 0 filterme a = filter isEven a
isEven is the function that we pass into our
filterme function. So when we run it:
:l FilterExample filterme [1,2,3,4,5,6]
filterme [1,2,3,4,5,6] will
return [2,4,6]. The function
isEven sets up the filter condition and is applied accordingly against the given values.
Let us take a look at another filter function:
module FilterExample2 where filterme a = filter (==3) a
filterme function takes one parameter and applies the conditional check of
(==3) against every value that gets passed through. So when we run the following:
:l FilterExample2 filterme [1,2,3,4,5,6,5,4,3,2,1]
The expected return result of
filterme [1,2,3,4,5,6,5,4,3,2,1] will be
The main takeaway from how filtering works in Haskell is that it needs to evaluate to true in order to be passed into the returned result. This means that the filter function must evaluate to a Boolean.
If you look at it, you can use map and filter almost interchangeably. Why? Because they both produce an array based on the parameters of the applying function. However, in
map, the function is applied with an expected returned result, regardless of the outcome. In contrast,
filter relies on a Boolean outcome.
Writing Haskell maps and filters isn’t hard once you get the general syntax down. Figuring out what you want as the end result and the steps to get you there is most likely the hardest part. You can create multiple levels of maps and filters, based on your required conditions. This means that you can mix and match maps and filters, as many times as you want.