Note 01: Monads are just monoids...
A monad is just a monoid in the category of endofunctors
I don’t have a lot of functional programming experience, but coming from a math background, I kinda just wanted to understand what this meant.
The following is my explanation of what it means, and how to relate it back to programming.
As a math undergrad, you’ve come across:
- Groups $(G, \cdot)$
- Sets $S$
- Topological Spaces $(X, \tau)$
and then you’re introduced to the structure preserving maps:
- for groups, homomorphisms $\phi: G \to H$ where $\phi(g_1 \cdot g_2) = \phi(g_1) \cdot \phi(g_2)$
- For sets: they don’t actually have structure, just regular maps $f: A \to B$
- Topological spaces: we have continuous maps $f: X \to Y$
I like to just think of groups first.
You can form a category $\mathbf{Grp}$ by taking all groups which become the objects of the category, along with all the homomorphisms between groups, which become morphisms of the category.
There are some details that I’m leaving out about what formally defines a category, but you can look that up yourself. I’m trying to build your intuition.
So you can see a Category is a higher-level thing. The funny thing is that they kind of map to the course divisions you’d take in school:
- Algebra studies $\mathbf{Grp}$
- Topology studies $\mathbf{Top}$
- Set theory: $\mathbf{Set}$
A functor $F: \mathcal{C} \to \mathcal{D}$ is a map between categories that preserves structures. It takes objects in $\mathcal{C}$ to objects in $\mathcal{D}$; and morphisms in $\mathcal{C}$ to morphisms in $\mathcal{D}$. For any morphism $f: X \to Y$ in $\mathcal{C}$, we get $F(f): F(X) \to F(Y)$ in $\mathcal{D}$.
An endofunctor is a functor $F: \mathcal{C} \to \mathcal{C}$ from one category to itself.
Here’s the kicker: you can ask yourself what if we consider first the set of all endofunctors on $\mathcal{C}$. Just think of it as a set, perfectly fine thing to do.
But we can actually give this the structure of a category. The morphisms are what’s known as natural transformations; It’s just a simple map that naturally arises when you try to take an endofunctor $F$, to another endofunctor $G$.
Let’s work out what that means:
A natural transformation $\eta: F \Rightarrow G$ is a collection of morphisms $\eta_X: F(X) \to G(X)$ for each object $X$ in $\mathcal{C}$, such that for any morphism $f: X \to Y$:
$$\eta_Y \circ F(f) = G(f) \circ \eta_X$$
Now let me introduce you to another category called $\mathbf{Type}$. Let’s pretend that we’re working with Typescript for the rest of this article. The objects of this category are actually all the types in Typescript. Examples include: string, number, Array<T> but also function types like string -> number etc.
The morphisms of the category $\mathbf{Type}$ are the actual functions in typescript. Like x => 2*x etc.
A monad on $\mathbf{Type}$ consists of:
- An endofunctor $M: \mathbf{Type} \to \mathbf{Type}$
- A natural transformation $\eta: \text{Id} \Rightarrow M$ (called "unit" or "return")
- A natural transformation $\mu: M \circ M \Rightarrow M$ (called "join" or "flatten")
These must satisfy associativity and identity laws: $$\mu \circ M\mu = \mu \circ \mu M$$ $$\mu \circ M\eta = \mu \circ \eta M = \text{id}_M$$