Free monad transformers
This post explains categorically the free construction of free monad
transformers which can be found in the free library on Hackage.
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Free.Monad.Trans where
import Control.MonadMonad transformers are represented by the following type class.
lift is a monad morphism between m and t m. monadTrans
witnesses the fact that a monad transformer transforms monads into monads; it
is not as general as it could be, but we will not need any more power here.
class MonadTrans t where
lift :: Monad m => m a -> t m a
monadTrans :: Monad m => (Monad (t m) => t m a) -> t m aFreeT is a functor
newtype FreeT f m a = FreeT { runFreeT :: m (FreeF f a (FreeT f m a)) }
data FreeF f a b = Pure a | Free (f b)
deriving FunctorInstances are left in the appendix.
The FreeT type maps a functor f :: * -> * to a monad transformer
FreeT f :: (* -> *) -> (* -> *), and that mapping should be functorial.
To define that functor, we must first spell out the categories defining the
domain and codomain.
This will make FreeT a functor between categories of functors, which
is the kind of remark that makes category theory notoriously confusing.
Domain and codomain
The domain is the category of functors f :: * -> * and natural
transformations; the codomain is a category of monad transformers, but it may
seem unclear what its morphisms should be. We will try to describe the
categorical structures of the domain and codomain in parallel.
A functor f :: * -> * maps a type a :: * to a type f a :: *.
A monad transformer t :: (* -> *) -> (* -> *) maps a monad
m :: * -> * to a monad t m :: * -> *.
Types form a category, where morphisms are functions.
Monads form a category, where morphisms are monad morphisms.
A natural transformation h :: f ~> g (a “functor morphism”)
between two functors f, g :: * -> * maps a type a :: *
to a function h @a :: f a -> g a.
type f ~> g = forall a. f a -> g aA monad transformer morphism k :: t ~~> u between monad transformers
t, u :: (* -> *) -> (* -> *) maps a monad m,
to a monad morphism k @m :: forall a. t m a -> u m a.
type t ~~> u = forall m a. Monad m => t m a -> u m aA natural transformation h commutes with the functorial mapping fmap.
h @b . fmap @f = fmap @g . h @a
A monad transformer morphism k “commutes” with lift.
k @m . lift @t = lift @u
Mapping morphisms
To be a functor, FreeT should map natural transformations f ~> g
to monad transformer morphisms FreeT f ~~> FreeT g. This mapping is called
transFreeT in the free library.
-- Only one of (Functor f) or (Functor g) is actually necessary for the
-- implementation.
transFreeT :: forall f g. Functor g => (f ~> g) -> (FreeT f ~~> FreeT g)
transFreeT h = FreeT . trans . runFreeT
where
trans :: forall m a. Monad m
=> m (FreeF f a (FreeT f m a))
-> m (FreeF g a (FreeT g m a))
trans = fmap @m (fmap @(FreeF g a) (transFreeT h) . transFreeF h)
transFreeF :: (f ~> g) -> (FreeF f a ~> FreeF g a)
transFreeF _ (Pure a) = Pure a
transFreeF h (Free f) = Free (h f)And that mapping should preserve composition.
transFreeT id = id
transFreeT (h . i) = transFreeT h . transFreeT i
Semi-proof
For example, the left hand side of the first equation reduces to:
transFreeT id = FreeT . fmap (transFreeT id) . runFreeT
And there’s probably an argument (by induction?) to conclude from there.
FreeT is a left adjoint
A free functor is a left adjoint to a forgetful functor.
Here, FreeT maps a functor f to a monad transformer FreeT f. A
corresponding “forgetful functor” should somehow map a monad transformer t
to a functor Forget t. Let us find a good functor.
If we use t to transform a well-chosen monad, then we get another monad,
which is also a functor, exactly what we’re looking for. We shall choose the
identity monad, as it is the most “neutral” of them in some sense. This may be
abstractly motivated by the fact that it is the initial object in the category
of monads and monad morphisms. It is most straightforwardly represented as a
newtype:
newtype Identity' a = Identity' aBut it will be a bit more convenient to use the following equivalent representation:
type Identity a = forall m. Monad m => m aThen, the forgetful functor is obtained as:
type Forget t a = forall m. Monad m => t m aWe shall use the definition of adjoint functors by Hom isomorphism: there
is a natural isomorphism between f ~> Forget t and FreeT f ~~> t.
In particular, it consists of a bijection given by the following functions.
Imagine f as specifying elements of syntax. The first direction means
that if we can interpret these elements individually, then we can interpret an
AST as a whole. This is foldFreeT.
foldFreeT
:: (Functor f, MonadTrans t) => (f ~> Forget t) -> (FreeT f ~~> t)
foldFreeT h = k
where
k (FreeT m) = monadTrans $ lift m >>= \v -> case v of
Pure a -> return a
Free f -> h f >>= kThe other direction seems less useful; it says that every monad transformer
morphism out of a free monad transformer can be decomposed as a foldTreeT
of some natural transformation, which is equivalent to a straightforward
restriction of that morphism.
restrict :: Functor f => (FreeT f ~~> t) -> (f ~> Forget t)
restrict k = k . FreeT . return . Free . fmap returnThe bijection we just gave is natural, making this diagram commute for all
k :: FreeT f ~~> t, i :: g ~> f and l :: t ~~> u,
. restrict
(FreeT f ~~> t) -> (f ~> Forget t)
| |
dimap i (forget l) | | dimap (transFreeT i) l
v restrict v
(FreeT g ~~> u) -> (g ~> Forget u)
-- or as an equation --
forget l . restrict k . i = restrict (l . k . transFreeT i)
where forget is the forgetful functorial mapping:
forget :: (t ~~> u) -> (Forget t ~> Forget u)
forget k = kand dimap is a bifunctorial mapping:
dimap :: (a -> b) -> (c -> d) -> (b -> c) -> (a -> d)
dimap i l k = l . k . iThis was a fun exercise in category theory. After figuring it out, I was
surprised to see that foldFreeT was not in free, but now it is.
Appendix
instance (Functor f, Monad m) => Functor (FreeT f m) where
fmap = liftMinstance (Functor f, Monad m) => Applicative (FreeT f m) where
pure = return
(<*>) = apinstance (Functor f, Monad m) => Monad (FreeT f m) where
return = FreeT . return . Pure
FreeT m >>= f = FreeT $ m >>= \v -> case v of
Pure a -> runFreeT (f a)
Free w -> return (Free (fmap (>>= f) w))instance Functor f => MonadTrans (FreeT f) where
lift = FreeT . fmap Pure
monadTrans t = t