Providence Salumu
I thought I would share one of my favorite constructions in Haskell, namely that adjoint functors give rise to monads. Although it’s a trivial result in category theory how it manifests in Haskell is quite lovely.
A Functor in Haskell maps objects and morphism (i.e. functions) in a subcategory of Hask to objects and morphisms of another category.
class Functor f where
fmap :: (a -> b) -> f a -> f b
And satisfies the functor laws:
fmap id = id
fmap (a . b) = (fmap a) . (fmap b)
In commutative diagrams we draw objects as points and morphisms as arrows. In a string diagrams we invert this and draw morphisms as points and objects as lines.
Functor composition is defined for \(F : \mathcal{A} \rightarrow \mathcal{B}\), \(G : \mathcal{B} \rightarrow \mathcal{C}\) as \(G \circ F : \mathcal{A} \rightarrow \mathcal{C}\), and is drawn with parallel lines.
newtype FComp f g a = C { unC :: f (g a) }
instance (Functor f, Functor g) => Functor (FComp f g) where
fmap f (C x) = C (fmap (fmap f) x)
Generally the composition of functors \(F \circ G\) is written simply as \(FG\). Composition diagrammatically allows us to collapse adjacent segments in our string diagram.
The identity functor ( \(\text{Id}\) ) is the functor that maps each morphism and object to itself.
newtype Id a = Identity { unId :: a }
instance Functor Id where
fmap f x = Identity (f (unId x))
Composition with the identity functor forms an identity relation:
\[ F \circ \text{Id}_B = F \\ \text{Id}_A \circ F = F \]
As witnessed by the expressions:
left :: Functor f => FComp f Id a -> f a
left (C a) = fmap unId a
right :: Functor f => f a -> FComp f Id a
right a = C $ fmap Identity a
We’ll follow the convention to omit the identity functor, and it is shown as a dotted line in subsequent string diagrams.
A natural transformation in our context will be a polymorphic function associated with two Haskell functor instances f
and g
with type signature (Functor f, Functor g) => forall a. f a -> g a
. Which could be written with the following type synonym.
type Nat f g = forall a. f a -> g a
The identity natural transform mapping a functor \(F\) to itself is written \(1_F\) and in Haskell is just (id
). The composition of natural transformations follows the associativity laws, shown below:
The final interchange law states that we can chase the natural transformations through the functors horizontally or compose natural transformation between functors vertically and still arrive at the same result.
\[ (\alpha \beta) \circ (\alpha' \beta') = (\alpha \alpha') \circ (\beta \beta') \]
type NatComp f f' g g' = forall a. f' (f a) -> g' (g a)
vert :: (Functor f, Functor f', Functor g, Functor g') =>
Nat f' g' -> Nat f g -> NatComp f f' g g'
vert a b x = a (fmap b x)
horiz :: (Functor f, Functor f', Functor g, Functor g') =>
Nat f' g' -> Nat f g -> NatComp f f' g g'
horiz a b x = fmap b (a x)
By the interchange law horiz
and vert
must be interchangable under composition. For natural transformations a, b, a', b'
in Haskell we have the equation:
(a . b) `vert` (a' . b') == (a `horiz` a') . (b `horiz` b')
A diagram example for a natural transformation \(\eta : 1_\mathcal{C} \rightarrow {FG}\) between the identity functor and the composition functor of \(FG\) would be drawn as:
An isomorphism \(F \cong G\) implies that composition of functors is invertible in that \(F G = \text{Id}_C\) and \(G F = \text{Id}_D\). An adjoint \(F ⊣ G\) between a pair of functors \(F : D \rightarrow C\) and \(G : C \rightarrow D\) is a weaker statement that there exists a pair of associated natural transformations \((F, G, \epsilon, \eta)\) with:
\[ \epsilon : FG \rightarrow 1_\mathcal{C} \\ \eta : 1_\mathcal{D} \rightarrow FG \]
Such that the following triangle identities hold:
\[ (\epsilon F) \circ (F \eta) = 1_F \\ (G \epsilon) \circ (\eta G) = 1_G \]
These are drawn below:
In terms of the categories \(C,D\) an adjoint is in some sense a “half-isomorphism” or “almost inverse” but some structure is lost in one direction.
\(\eta\) and \(\epsilon\) are also referred to respectively as the unit and counit.
In Haskell we have the following typeclass which unfortunately requires a functional dependency in order for type inferencer to deduce which fmap
is to be used:
class (Functor f, Functor g) => Adjoint f g | f -> g, g -> f where
eta :: a -> g (f a)
epsilon :: f (g a) -> a
There are also two other natural transformations (\(\phi, \psi\)) which together with the adjoint functor pair form an adjunction. The adjunction can be defined in terms of the adjoint pair and this is most convenient definition in Haskell
\[ \psi \epsilon = 1_F \\ \phi \eta = 1_G \]
phi :: Adjoint f g => (f a -> b) -> a -> g b
phi f = fmap f . eta
psi :: Adjoint f g => (a -> g b) -> f a -> b
psi f = epsilon . fmap f
Notably \(\phi\) and \(\psi\) form an isomorphism between the set of functions (a -> g b)
and (f a -> b)
which is the same relation as the above triangle identities. Alternatively \(\eta\) and \(\epsilon\) can be expressed in terms of \(\phi\) and \(\psi\).
phi eta = id
psi epsilon = id
From the Haskell Prelude we have the canonical adjoint namely curry
and uncurry
:
\[ \text{curry} \quad ⊣ \quad \text{uncurry} \]
instance Functor ((,) a) where
fmap f (x,y) = (x, f y)
instance Functor ((->) a) where
fmap f g = \x -> f (g x)
Which we can construction an Adjoint instance from these two functor instances:
instance Adjoint ((,) a) ((->) a) where
eta x y = (y, x)
epsilon (y, f) = f y
We can check that the triangle equalities hold for this definition by showing both \((\epsilon F) \circ (F \eta)\) and \((G \epsilon) \circ (\eta G)\) reduce to the identity natural transformation ( id
).
a0 :: (a -> (b -> c)) -> a -> (b -> c)
a0 f = \f -> fmap (epsilon . fmap f) . eta
a0 f = fmap (\(y, f) -> g f y) . eta
a0 f = \x y -> f x y
a1 :: ((a, b) -> c) -> (a,b) -> c
a1 f = epsilon . fmap (fmap f . eta)
a1 f = epsilon . fmap (\x y -> f (y, x))
a1 f = \(x, y) -> f (x, y)
We know a Monad is an endofunctor \(T : C \rightarrow C\) with two natural transformations \((T, \mu, \eta)\) with the usual laws:
\[ \mu \circ T \mu = \mu \circ \mu T \\ \mu \circ T \eta = \mu \circ \eta T = 1_T \\ \]
The geometric intuition is that the monad laws are reflected as topological properties of the string diagrams. Both \(\mu\) and \(\eta\) exhibit reflection symmetry and that we topologically straighten out \(\eta\) to yield the identity functor.
In Haskell we can normally construct the Monad type class from an Endofunctor and (\(\mu, \eta\)) or join
and return
.
class (Functor t) => Monad t where
eta :: a -> (t a)
mu :: (t (t a)) -> (t a)
(>>=) :: t a -> (a -> t b) -> t b
ma >>= f = mu . fmap f
return = eta
join = mu
What is not immediately apparent though is that every adjoint pair of functors gives rise to a monad \((T, \mu, \eta)\) over a category \(C\) induced by the composition of the functors to give rise to a endofunctor and natural transformations in terms of the unit and counit of the underlying adjunction:
\[ \begin{align} T &= G \circ F & : &C \rightarrow C \\ \mu &= G \epsilon & : &T^2 \rightarrow T \\ \end{align} \]
class Adjoint f g => Monad f g where
muM :: g (f (g (f a))) -> g (f a)
muM = fmap epsilon
etaM :: a -> g (f a)
etaM = eta
(>>=) :: g (f a) -> (a -> g (f b)) -> g (f b)
x >>= f = muM (fmap (fmap f) x)
The geometric intution for this is clear:
From the Monad we can then construct the Kleisli category in the usual way.
class (Adjoint f g, Category c) => Kleisli c f g where
idK :: c x (g (f x))
(<=<) :: c y (g (f z)) -> c x (g (f y)) -> c x (g (f z))
instance Monad f g => Kleisli (->) f g where
idK = eta
g <=< f = muM . fmap (fmap g) . f
instance Kleisli c f g => Monoid (c a (g (f a))) where
mempty = idK
mappend = (<=<)
In retrospect this is trivial, but importantly leads us to the more important question: Can we recover an adjunction from a monad. The answer is Yes…