Free monads in category theory

Part II of the series Free monads.
August 2, 2016

Forgetting how to multiply

It’s probably easiest to understand what a free monad is if we first understand forgetful functors1.

In category theory, a functor maps between categories, mapping objects to objects and morphisms to morphisms in a way that preserves compositionality2.

A forgetful functor is just a functor that discards some of the structure or properties of the input category.

For example, unital rings have objects (R, +, \cdot, 0, 1) , where R is a set, and (+, \cdot) are binary operations with identity elements (0, 1) respectively.

Let’s denote the category of all unital rings and their homomorphisms by \bf{Ring} , and the category of all non-unital rings and their homomorphisms with \bf{Rng} . We can now define a forgetful functor: \it{I}: \bf{Ring} \rightarrow \bf{Rng} , which just drops the multiplicative identity.

Similarly, we can define another forgetful functor \it{A}: \bf{Rng} \rightarrow \bf{Ab} , which maps from the category of rngs to the category of abelian groups. \it{A} discards the multiplicative binary operation, simply mapping all morphisms of multiplication to morphisms of addition.

Forgetting monoids

The forgetful functor \it{A} forgets ring multiplication. What happens if instead you forget addition? You get monoids! We can define monoids as the triple (S, \cdot, e) , where S is a set, \cdot is an associative binary operation, and e is the neutral element of that operation.

The forgetful functor \it{M}: \bf{Ring} \rightarrow \bf{Mon} maps from the category of rings to the category of monoids, \bf{Mon} , in which the objects are monoids, and the morphisms are monoid homomorphisms.

Monoid homomorphisms map between monoids in a way that preserves their monoidal properties. Given \mathcal{X} , a monoid defined by (X, *, e) , and \mathcal{Y} , a monoid defined by (Y, *’, f) , a function \it{\phi}: \mathcal{X} \rightarrow \mathcal{Y} from \mathcal{X} to \mathcal{Y} is a monoid homomorphism iff:

it preserves compositionality3:

\begin{equation}\phi(a * b) = \phi(a) *' \phi(b), \forall a\; b \in \mathcal{X}\end{equation}

and maps the identity element: \begin{equation}\phi(e) = f\end{equation}

Translating into Haskell, if phi is a monoid homomorphism between monoid X to monoid Y, then:

phi (mappend a b) == mappend (phi a) (phi b)  -- (1)

phi (mempty :: X) == mempty :: Y              -- (2)

For example, we can define a monoid homomorphism that maps from the list monoid to the Sum monoid, the monoid formed from the natural numbers under addition:

import Data.Monoid

listToSum :: [a] -> Sum Int
listToSum = Sum . length

If it’s too difficult (or we can’t be bothered) to derive a formal proof, we can use QuickCheck to test properties of functions. Let’s quickly check if listToSum is actually a monoid homomorphism:

import Test.QuickCheck

-- (1)
homomorphism :: [()] -> [()] -> Bool
homomorphism a b =
  phi (mappend a b) == mappend (phi a) (phi b)
    where phi = listToSum
ghci> quickCheck homomorphism -- (1)
OK, passed 100 tests.

ghci> listToSum (mempty :: [a]) == mempty :: Sum Int -- (2)
True

Let’s forget some more things with yet another forgetful functor, \it{U}: \bf{Mon} \rightarrow \bf{Set} 4.

\bf{Set} is a category where the objects are sets, and the arrows are just plain functions. So \it{U} will map every monoid in \bf{Mon} to its underlying set, and every monoid homomorphism to a plain function.

Sum Int would just become Int, listToSum would just become length, mappend :: Sum a would map to (+), and so on. We forget that any of these things formed a monoid.

Natural Transformations

Moving our discussion from forgetful functors to free constructions requires the concept of natural transformations. Recall that a functor F: \cal{C} \rightarrow \cal{D} must take all objects X \in \cal{C} to F(X) \in \cal{D} , and all morphisms f: X \rightarrow Y \in \cal{C} to F(f): F(X) \rightarrow F(Y) \in \cal{D} , such that the following diagram commutes:

\require{AMScd} \begin{CD} X @>{f}>> Y\\ @V{F}VV @VV{F}V \\ F(X) @>{F(f)}>> F(Y)\end{CD}

This diagram says that it doesn’t matter if we start with X , apply F and then F(f) , or start with X and instead apply f and then F - we always end up with F(Y) . The functor has mapped between categories in a way that preserves the internal structure of the original category.

A natural transformation is a similar sort of structure-preserving5 mapping, except instead of mapping between categories, it maps between functors.

Given functors F, G: \cal{C} \rightarrow \cal{D} , a natural transformation \eta is a morphism between functors such that:

  1. For all X \in \mathcal{C} , there exists a morphism, \eta_{X}: F(X) \rightarrow G(X) , where F(X), F(G) \in \mathcal{D} 6

  2. For every morphism f: X \rightarrow Y \in \mathcal{C} , the following diagram- a naturality square- commutes:

\require{AMScd} \begin{CD} F(X) @>{F(f)}>> F(Y)\\ @V{\eta_{X}}VV @VV{\eta_{Y}}V \\ G(X) @>{G(f)}>> G(Y)\end{CD}

This means we’re making a rather strong claim about the properties of \eta : G(f) \circ \eta is the same as \eta \circ F(f) !

Adjunctions

Let’s consider two functors going in opposite directions, \it{F}: \mathcal{C} \rightarrow \mathcal{D} and \it{G}: \mathcal{D} \rightarrow \mathcal{C} .

F and G aren’t just any old functors though- they’re equipped with a natural isomorphism:

\alpha: \cal{D}\textrm{(}\it{FX, Y}\textrm{)} \cong \cal{C}\textrm{(}\it{X, GY}\textrm{)}

where the isomorphism is natural in X and Y .

Simply saying these hom-sets are naturally isomorphic is rather imprecise. We can pin down the naturality of \alpha by saying that certain natural transformations hold. But to define a natural transformation we need to define some functors!

We can define a natural transformation between these hom-functors from X \in \mathcal{C} to Y \in \cal{D} , fixing Y :

\cal{D}\textrm{(}\it{F\_, Y}\textrm{)}

\cal{D}\textrm{(}\it{\_, GY}\textrm{)}

We can use another notation to make their functorish nature more apparent:

X \mapsto \textrm{hom}(FX, Y): \cal{C}^{op} \rightarrow \bf{Set}

X \mapsto \textrm{hom}(X, GY): \cal{C}^{op} \rightarrow \bf{Set}

These functors take every object X \in \mathcal{C}^{op} to a hom-set of morphisms in \cal{D} , so it’s perfectly valid to ask for a natural transformation between them:

\require{AMScd} \begin{CD} \cal{D}\textrm{(}\it{FX^{\prime},Y}\textrm{)}@>{\alpha}>> \cal{C}\textrm{(}\it{X^{\prime}, GY}\textrm{)} \\ @V{\_ \circ Ff}VV @VV{\_ \circ f}V \\ \cal{D}\textrm{(}\it{FX,Y}\textrm{)} @>{\alpha}>> \cal{C}\textrm{(}\it{X, GY}\textrm{)} \end{CD}

So for every morphism f: X^{\prime} \rightarrow X \in \cal{C} , applying \alpha and then precomposing with f is the same as precomposing with Ff and then applying \alpha . That’s naturality in X . Naturality in Y is much the same, except we fix X , and get functors from \cal{D} \rightarrow \bf{Set} :

\require{AMScd} \begin{CD} \cal{D}\textrm{(}\it{FX,Y}\textrm{)}@>{\alpha}>> \cal{C}\textrm{(}\it{X, GY}\textrm{)} \\ @V{g \circ \_}VV @VV{Gg \circ \_}V \\ \cal{D}\textrm{(}\it{FX,Y^{\prime}}\textrm{)} @>{\alpha}>> \cal{C}\textrm{(}\it{X, GY^{\prime}}\textrm{)} \end{CD}

for all mophisms g: Y \rightarrow Y^{\prime} \in \cal{D} .

We can think of \alpha as a pair of hom-functors7 that take \mathcal{C}^{op} \rightarrow \bf{Set} , and a pair of functors that take \cal{D} \rightarrow \bf{Set} , such that each pair of functors creates a bijection between their corresponding sets, satisfying the above naturality conditions.

We describe this functorial relationship by saying that F is left adjoint to G , or F \dashv G .

Free monoids

Armed with the ability to talk about the “adjointness” of functors, we can now examine what happens when we take U to be a forgetful functor, when F \dashv U .

If U is a forgetful functor that discards some information about its domain, F must be able to “reconstruct” enough to go from D to C . The left adjoint to a forgetful functor is always a free functor!

Returning to our monoid example, if we take U to be U: \bf{Mon} \rightarrow \bf{Set} , the left adjoint to U is the free functor F: \bf{Set} \rightarrow \bf{Mon} .

This means there must be a natural isomorphism, \alpha , that creates a bijection between hom-sets of F and U , such that all functions a \in \bf{Set} to an underlying set of \bf{Mon} uniquely determines a monoid homomorphism that’s natural in a and b :

\alpha: \bf{Mon}\textrm{(}\it{Fa} \rightarrow b) \cong \bf{Set}\textrm{(}\it{a} \rightarrow \it{Ub}\textrm{)}

and vice-versa.

How could we construct F so that the above conditions are met? Spoiler alert: we can just use List! Let’s try to translate \alpha , and its inverse, into pseudo-Haskell8.

-- Just delete the monoid constraint
u :: Monoid m = m

alpha :: (List a -> Monoid m) = (a -> u (Monoid m))

alpha' :: (a -> u (Monoid m)) -> (List a -> Monoid m)

Now we can translate this into actual Haskell9. Since u just removes the monoid constraint, we can substitute all instances of u (Monoid m) with simply m, and we can use the real list constructor and type constraint syntax:

import Data.Monoid

alpha :: Monoid m => (a -> m) -> ([a] -> m)
alpha g xs = mconcat $ map g xs

alpha' :: Monoid m => ([a] -> m) -> (a -> m)
alpha' h x = h [x]

To prove that alpha actually forms a natural isomorphism, we need to show that alpha . alpha' = id:

-- Proof that alpha . alpha' = id

alpha . alpha'
-- eta expand
= \h x -> alpha (alpha' h) x

-- substitute definition of alpha'
= \h x -> alpha h [x]

-- substitute definition of alpha
= \h x -> mconcat (map h [x])

-- map f [x] = [f x]
= \h x -> mconcat ([h x])

-- mconcat [x] = x
= \h x -> h x

-- eta-reduce
= \h = h

-- definition of id
= id

and in the other direction, that alpha' . alpha = id:

-- Proof that alpha' . alpha = id

alpha' . alpha
-- eta-expand
= \g xs -> alpha' (alpha g) xs

-- substitute definition of alpha
= \g xs -> mconcat (map (alpha g) xs)

-- eta-expand
= \g xs -> mconcat (map (\x -> alpha g x) xs)

-- substitute definition of alpha'
= \g xs -> mconcat (map (\x -> g [x]) xs)

-- map (f . g) = map f . map g
= \g xs -> mconcat (map g (map (\x -> [x]) xs))

-- free theorem
= \g xs -> g (mconcat (map (\x -> [x]) xs))

-- mconcat [[a],[b],[c]] = [a,b,c]
= \g xs -> g xs

-- eta-reduce
= \g -> g

-- definition of id
= id

So it follows that the list does indeed form a free monoid! Interestingly, what we’ve already defined as alpha is just foldMap:

alpha :: Monoid m => (a -> m) -> ([a] -> m)

foldMap :: Monoid m => (a -> m) -> [a] -> m

So in more Haskellish terms, we map each element of a list to a monoid, and then combine the results using the structure of that monoid.

ghci> foldMap Product [2,4,6]
Sum {getSum = 12}

ghci> foldMap Product [2,4,6]
Product {getProduct = 48}

Of course, foldMap is really defining a monoid homomorphism, which means it should map the identity element:

ghci> foldMap Product []
Product {getProduct = 1}

ghci> foldMap Sum []
Product {getSum = 0}

…and preserve compositionality:

homomorphism :: [Int] -> [Int] -> Bool
homomorphism a b = phi (a ++ b) == phi a `mappend` phi b
  where phi = foldMap Sum
ghci> quickCheck homomorphism
OK, passed 100 tests.

Free Monads

Now let’s take a look at free monads. Of course, monads are just monoids in the category of endofunctors, so we can apply what we’ve already learned! A monad is an endofunctor T: \cal{C} \rightarrow \cal{C} equipped with natural transformations \eta: 1_{\cal{C}} \Rightarrow T and \mu: T^{2} \Rightarrow T , obeying the obvious10 axioms of identity and associativity. The \bf{Monad} category has monads as objects, and monad homomorphisms as arrows.

We can define a forgetful functor, U: \bf{Monad} \rightarrow \bf{End} , that maps from the category of monads to the category of endofunctors. The category of endofunctors, \bf{End} , has endofunctors as objects and natural transformations as arrows, so U should be a forgetful functor such that:

  • For every monad T , U will forget \eta and \mu , and just give us the underlying endofunctor T .
  • For every monad homomorphism \phi , U will give us a natural transformation in \bf{End} .

Now we can see what behaviour F should have, when F \vdash U :

  • For every endofunctor A , F A should be a monad.
  • For every natural transformation \eta: A \Rightarrow B , F \eta should be a monad homomorphism.
  • The isomorphism F A \Rightarrow B \cong A \Rightarrow U B should be natural in A and B .

Again, this makes very strong claims about the behaviour of F . It turns out that the following construction11 satisfies all these criteria:

We’re effectively asking for the existence of

data Free f a = Pure a | Free (f (Free f a))

instance Functor f => Monad (Free f) where
  return a = Pure a
  Pure a >>= f = f a
  Free m >>= f = Free (fmap (>>= f) m)

The monadic bind operation (>>=) can be defined in terms of “substitution followed by renormalization”:

Monad m => m a -> (a -> m b) -> m b
m >>= f = join (fmap f m)

In conventional monads, we substitute the a in our monad m a with m b, to get m m b, and then we renormalize with \mu: T^{2} \Rightarrow T (which we call join in Haskell) to get m b. Free monads still perform the substitution of the underlying functor, but because Free type is defined recursively as Free (f (Free f a)), we effectively get \mu: T^2 \Rightarrow T for free by by sticking another layer of Free on top. It’s a lossless process; everything you’ve joined is retained12. In fact, Free looks suspiciously like List:

data List a = Nil | Cons (List a)

data Free f a = Pure a | Free (f (Free f a))

So Free is basically just a list of functors! When we defined the free monoid, the natural isomorphism constraint basically forced us into defining foldMap, which mapped each element of the list to a monoid, and then used the structure of that monoid to join the resulting elements:

foldMap :: Monoid m => (a -> m) -> [a] -> m
foldMap f xs = mconcat $ map f xs

Now we’re going to do the same for the free monad, by defining the natural transformation foldFree, and its inverse, foldFree':

foldFree :: (Functor f, Monad m) =>
  (forall a . f a -> m a) -> Free f a -> m a
foldFree' :: (Functor f, Monad m) => 
  (forall a . Free f a -> m a) -> f a -> m a

Doing that is as simple as following the types:

foldFree _ (Pure x) = return x
foldFree phi (Free xs) = join $ phi $ fmap (foldFree phi) xs

foldFree' psi = psi . Free . (fmap Return)

Proving that foldFree . foldFree' = id is left as an exercise for the reader.


  1. As far as I understand, there’s no formal way of describing the “forgetfulness” of a functor.

  2. A functor “preserves compositionality” if two morphisms of the input category compose to form a third morphism, such that the image of those two morphisms under the functor also compose to form the image of the third morphism.

  3. All homomorphisms have one constraint in common: they must preserve compositionality. We can be generalise the homomorphism constraint for any n -ary operation; a function \it{f}: A \rightarrow B is a homomorphism between two algebraic structures of the same type if: \it{f}(\mu_{A}(a_{1}, \ldots, a_{n})) = \mu_{B}(f(a_{1}), \ldots, f(a_n)) for all a_{1}, \ldots, a_{n} \in A

  4. Technically, in Haskell we’d be mapping to the category \bf{Hask} , the category of Haskell types.

  5. It’s actually exactly the same kind of structure preservation, because functors form a category where the objects are functors and the morphisms are natural transformations.

  6. We call \eta_{X} “the component of \eta at X

  7. Category theory brings a great richness of perspectives, allowing us to think about relationships in whatever way that happen to suit whatever we’re trying to talk about.

  8. I’m not sure of a way to write a polymorphic function that “forgets” a monoid constraint, so we’ll just wave our hands a bit until we get to Real Haskell.

  9. This might look a bit strange- even though we’ve supposedly “forgotten” that m is a monoid in alpha', the type variable m is still bound by the monoid type constraint. We can cheat, though, and explicitly parameterize a forgetful function Monoid m => m -> b:

    alpha Monoid m=>(b->m)->(a->b)->([a]->m)
    alpha m g xs = mconcat $ map (m . g) xs
    
    alpha' Monoid m=>(m->b)->([a]->m)->(a->b)
    alpha' m h x = m . h $ [x]
  10. Obvious to experienced category theorists, at least.

  11. The way this construction defines the bind operator results in a quadratic asymtotic complexity. Janis Voigtlander describes an approach for reducing the asymtotic complexity to linear in his paper Asymtotic Improvement of Computations over Free Monads.

  12. This gives an intuition for why any natural transformation between endofunctors, f: A \Rightarrow UB , can be fed to the free functor to form a monad homomorphism, Ff: FA \Rightarrow B . Just like the free monoid, we don’t “throw away” any information about the free construction beyond what’s defined by the underlying category.