Functors in Haskell

In this post I'll write about functors only in programming terms, specifically in Haskell.

In the last post we discussed the MaybeMaybe functor. A functor is not itself a type, it's a type constructor as given by the definition of the typeclass in Haskell:

The FunctorFunctor typeclass requires an implemention of fmapfmap for every instance of that typeclass. fmapfmap is a higher order function which takes a function aba \rightarrow b and "lifts" it into a function that acts on the function fafbf a \rightarrow f b which itself is a mapping from functor-to-functor.

This means really that we can take any ordinary function and apply it to a functor, which then will return a functor.

List is a functor

A list in Haskell is a coproduct type defined recursively as:

The built-in list type has a functor instance. Here's the implementation:

We simply apply the function to the head of the list, then fmapfmap that function over the tail of the list and combine them.

The Reader functor

This is something that is, on the surface, quite different to the functor examples shown before (MaybeMaybe and ListList).

In Haskell functions are types. A partially applied function is also a type in Haskell - here we take a function aba \rightarrow b and partially apply it ()a(\rightarrow) a - it's a function that's awaiting a second type argument bb, this is also a type constructor. When it receives it's second type it becomes a full type - a total function.

This type constructor (partially applied function) defines a whole family of type constructors parameterized by the type aa. Here, in the context of the ReaderReader functor we want to take a type aa and map it into a type rar \rightarrow a. In order to show that ReaderReader is a functor we want to lift the original function aba \rightarrow b into (ra)(rb)(r \rightarrow a) \rightarrow (r \rightarrow b). In Haskell:

That is, we apply gg and then ff - it's the only way to make the two arguments typecheck. In pointfree form:

In simple terms it's a thing that takes a resource rr, (rar \rightarrow a) to which we apply a regular function aba \rightarrow b. Here's an example:

In other words we have ff as a partially applied function (3)(* 3) which is awaiting it's argument - we have a resource rr to which we pass as the first argument to the partially applied function gg (+100)(+100), the result of this (r+100)(r + 100) gets passed to ff which is ((r+100)3)((r + 100) * 3) - so the result is 303 given r=1r = 1 as above. Remember we are dealing with partially applied functions, so the whole process kicks off when we are given an rr. This is applied to the second function (+100)(+ 100) and the result of that (101)(101) is passed to the first function.

There is a lot more to the ReaderReader functor - it's actually a MonadMonad which itself "derives" from FunctorFunctor. This is just some of the detail in the context of functors.

Functor composition

This follows from the rules of category. Given the following function:

We have a function that takes a functor [a][a] to another functor Maybe[a]Maybe [a] which is itself a functor containing a functor. In order to apply some function ff to this we need to fmapfmap into the outer MaybeMaybe functor, and then fmapfmap again to break into the inner [][] functor. A simple example on a list of integers that may or may not have a tail:

Gaining intution

Functors can be thought of as a context in which some value(s) is/are stored. For example the MaybeMaybe functor is a context that may or may not contain a value. IOIO is a context that contains those values that come from I/O and are thus unpredictable, and so on.

This doesn't always make the most sense as functors like the list functor are better thought of as a container. There are other functors like the constant functor that don't map well to this context worldview, and so are better thought of as a thing that contains something, but we don't really care about what it contains. We only care about preservation of the structure of the categories upon which the functors act on.

In a nutshell the functor typeclass allows us to apply pure functions to values that are in a context/container - this means we are separate our program logic neatly into pure and effectful parts.