Samuel Evans-Powell

< Home


I've been learning about Haskell and Category Theory lately. In this series of posts I hope to collect my thoughts. Please pop me an email or open a pull request here if you want to make any suggestions/corrections, I'm still learning myself.

A data type that is an instance of Functor allows you to apply some function to values within the context of that Functor (a list of Integers can be thought of as Integer values within the context of a list). You're probably familiar with at least one instance of Functor: the List type in Haskell. The List type allows functions to be applied to values in the list using the map function:

map :: (a -> b) -> [a] -> [b]

In Haskell, for a type to be a Functor instance, it must implement the fmap function:

Note that fmap is just a generalization of the map function, with the list being replaced by the type parameter f.

fmap :: (a -> b) -> f a -> f b

That's it! Just one function.

The fmap function can be thought of as 'mapping' values of the type 'a' in the given functor (the domain) to values of the type 'b' in the same functor (codomain), where the given function (a -> b) performs that 'mapping'.

A simple Functor instance for Lists might look like this:

instance Functor (List) where
  fmap :: (a -> b) -> [a] -> [b]
  fmap _ []     = []
  fmap f (x:xs) = f x : fmap xs

The supplied function is simply applied to each element in the list.

The Haskell Maybe type is also an instance of Functor:

instance Functor (Maybe) where
  fmap :: (a -> b) -> Maybe a -> Maybe b
  fmap _ Nothing  = Nothing
  fmap f (Just a) = Just (f a)

Again, pretty simple: if the given Maybe has the value 'Nothing', return 'Nothing', otherwise apply the function to the value present.

Alright, how about something a bit more challenging:

instance Functor ((->) e) where

The function type constructor can be an instance of Functor too!

Yes, this can be very hard to wrap your head around. If you get lost here, don't worry! Come back to it later.


Let's start with an aside on kinds:

Just as types are used to classify values, kinds are used to classify types.

The kind of Bool is '∗':

:k Bool 
Bool :: *

The kind of Integer is '∗':

:k Integer
Integer :: *

:k [Integer]
[Integer] :: *

If something can be used as a value, it's type will have the kind '∗'.

:t True
True :: Bool

:k Bool
Bool :: *

True is a value whose type (Bool) has the kind '∗'.

Type constructors on the other hand have the kind '(∗ -> ∗)':

:k []
[] :: * -> *

:k Maybe
Maybe :: * -> *

They take something of kind '∗' (Such as Integer, [Integer], Bool, Maybe Integer, etc.) and produce something of kind '∗'. That is, it takes a value type and produces a new value type.

The Maybe type takes any type of kind '∗' and produces a value of a type whose kind is '∗':

:k Maybe
Maybe :: * -> *

:k Int
Int :: *

:k Maybe Int
Maybe Int :: *

The function type constructor takes two things of kind '∗' and produces something of kind '∗':

:k (->)
(->) :: * -> * -> *

:k ((->) Integer)
((->) Integer) :: * -> *

:k ((->) Integer Bool)
((->) Integer Bool) :: *

-- Rewritten with (->) infix:
:k (Integer -> Bool)
(Integer -> Bool) :: *

Oh look, functions have the same kind as any other values!

Now, let's look at the kind of Functor.

$ :k Functor
Functor :: (* -> *) -> Constraint

We can see that Functor takes something of kind '(∗ -> ∗)' (i.e. a type constructor) and produces a Constraint. We won't look at constraints now, but the name should give you a sufficient idea of what they do.

It makes sense that we can create a Functor instance of Maybe because the kind of Maybe matches:

:k Maybe
Maybe :: * -> *

instance Functor Maybe where
-- ...

But, notice that the kind of ((->) e) (where e is any type) also matches:

:k ((->) Integer)
((->) Integer) :: * -> *

Therefore, we can make Functor instances of ((->) e), i.e. the function type constructor with one argument applied.

To make such an instance, we need an implementation of the fmap function:

fmap :: (a -> b) -> f a -> f b

Let's substitute f for our function type constructor, ((->) e):

fmap :: (a -> b) -> ((->) e a) -> ((->) e b)

And writing (->) infix:

fmap :: (a -> b) -> (e -> a) -> (e -> b)

Now that we have the type, let's try our hand at writing it (just match the types!):

fmap :: (a -> b) -> (e -> a) -> (e -> b)
fmap f g = f . g

(Convince yourself that this works). Hmm, this is just function composition and so can be re-written pointfree as:

instance Functor ((->) e) where
  fmap :: (a -> b) -> (e -> a) -> (e -> b)
  fmap = (.)

Phew, our Functor instance for the function type constructor. Essentially this allows us to apply a function to the resulting value of another function (function composition).

I hope this also demonstrates the power of 'following the types' of functions (and kinds of types) when trying to understand functional programs. Do this often!


Author: Samuel Evans-Powell

Created: 2018-07-25 Wed 15:47