Wei Yen

home · github · gitlab · linkedin

A gentle introduction to functors

We can think of a list of some type as a wrapper around that type. For example, a list of strings contains strings [citation needed].

list_of_strings = ["Adam", "Bobby", "Caroline"]

If we have a length function that converts strings into integers:

def length(some_string):
    return len(some_string)

length("Adam") == 4  # str -> int
length("Bobby") == 5  # str -> int
length("Caroline") == 8  # str -> int

Then we can trivially get from a list of strings to a list of integers, just by using the same length function and the map operation:

>>> map(length, ["Adam", "Bobby", "Caroline"])
[4, 5, 8]

The map function is a powerful pattern that allows us to operate on wrapped types, just by defining how to operate on their elements. And anything that can be mapped over like that, are called functors.

The map function has the following signature:

(a -> b) -> f a -> f b

That is, it takes a function that transforms type a to type b, and gives us a function that transforms f a to f b, where f a is a functor that wraps the type a.

It's important to point out that f aren't limited to lists - sets and trees can be mapped over in a similar manner.

It doesn't even have to be collections either. Consider the Option a type, which represents a "container" that may contain some value of type a, or nothing. It is a common pattern to say, I have this thing that may be some value, or it might be null. If I have an actual value, then do a -> b; otherwise, just give me null. Either way, I want an Option of type b.

That operation kinda looks like

Option a -> (a -> b) -> Option b

Which after some rearranging, looks a lot like the definition of map above! Indeed, Option types can be thought of as functors as well.

In summary, a functor is a "wrapper" of one type, that can be transformed into a "wrapper" of a different type, just by applying a function on its elements. Simple as that.