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:
. 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: Just
and 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).
Again, using 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 fmap
:
. 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.
Conclusion
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’
fmap
implementation will have longer names, which would encourage you to just use the universal, 4-letterfmap
function instead.↩︎