Functors for Newbies
This is a tutorial on functors. It is inspired by the already-excellently written article here.
What is a Functor?
In Haskell, there is a Functor type class. If a type is an instance of the Functor class, it is essentially a container type that adds context to any value.
The Functor class is defined as follows:
. That looks a bit weird, so I will rewrite it:
. So, for a container type to be a real Functor, it has to be able to take in a function, and be able to modify the value contained inside itself without changing the context. This is extremely useful. Let’s look at some functors.
The List Functor
Imagine you have a list with some numbers in it:
xs = [1, 2, 3]. Also imagine you already wrote this function below:
1 2 3 4
. Now, you’d like to use
makeStatement on the list
xs, which has a type
[Int], so that every
Int inside the list gets transformed according to
makeStatement. Thankfully, List already has an instance of the Functor class, so we can do it like this:
. Though, since List defines
fmap with a shorter-named
map, we can just use
map instead of
fmap to save ourselves a keystroke.1 While we’re at it, we might as well drop the
xs because it’s redundant.
This is immensely useful. Without the Functor class, you’d have to use pattern matching to manually extract the values contained in the list first before applying functions on them. Imagine a sophisticated, recursive binary tree with millions of elements — as long as the container type has an instance for Functor, you can just use a one-liner
fmap to universally apply the function at hand to every single element inside the tree (and rest assured that the context for all the individual values and their interrelationships remain untouched — a win win!).
For empty lists, You can still pass along a function with
fmap into them, but nothing will happen because they are empty (that is, there are no values inside to modify with the function).
The Maybe Functor
Let’s look at the Maybe type just to prove that they are also functors.
Maybe is a container, except that it can only contain just 1 element (unlike List which can hold multiple elements). Maybe has two constructors:
Nothing; you can use
Just to put a value into the Maybe type, like how you can use the
(:) function to put a single item into a list. Alternatively, you can use
Nothing to denote the empty Maybe container (like
 for lists).
1 2 3 4 5
fmap on an empty container will return the empty container as-is:
The Tuple Functor?
You might be wondering — what about tuples? Well, because tuples hold different types in a single container, it is impossible to implement
fmap for it. Recall the type signature required for
. Notice that the container must contain a single type
apples — tuples by their nature have arbitrary numbers of different types (they have apples and oranges in them already). This is the reason why we don’t (and can’t) have an
fmap function for tuples.
Functors are awesome. If you ever define your own custom container type, be sure to make an instance for Functor to make life easy for everyone.
I imagine other types’
fmapimplementation will have longer names, which would encourage you to just use the universal, 4-letter