Formal expressions and how to evaluate them
Take a set and form the set of finite lists of elements of — formal products with no rule for multiplying yet. Three pieces of structure are already there, before any algebra is chosen:
- every element is a one-letter word: ;
- a list of lists can be flattened: ;
- a monoid is exactly a set with a way to evaluate words, , compatible with the first two.
The same pattern appears far from monoids. Subsets of subsets can be flattened by union, and a complete lattice is a set where every subset can be evaluated to its supremum. Probability distributions over probability distributions can be flattened by averaging, and expectation evaluates a distribution of numbers to a number. On a topological space, Godement's "standard construction" sends a sheaf to its sheaf of discontinuous sections and builds sheaf cohomology by iterating. In a programming language, a computation that may fail, branch or print returns a value wrapped in that effect, and running two such computations in sequence requires flattening.
A monad is the common shape: a functor with a unit and a flattening obeying associativity and unit laws. Its algebras are the structures that describes. This lesson develops the theory — algebras, the Kleisli and Eilenberg–Moore categories, the correspondence with adjunctions, and Beck's monadicity theorem — and then looks at the same idea through the eyes of topologists, algebraists, probabilists and computer scientists.
Wrap, flatten, evaluate
Read as "-shaped things wrapped in a -structure": words in , subsets of , distributions on , terms built from variables in , computations returning an .
- wraps a plain element as a trivial structure: the one-letter word, the singleton, the point mass, the variable, the computation that does nothing and returns .
- flattens two layers of structure into one: concatenation of words, union of subsets, averaging of distributions, substitution of terms into terms, running a computation that returns a computation.
The laws say that flattening three layers does not depend on which two layers you flatten first, and that wrapping then flattening (on the inside or the outside) does nothing. They are the laws of a monoid — written one dimension up, with composition of functors in place of multiplication. That analogy is precise: monads on are the monoid objects in the monoidal category of endofunctors of (see monoidal categories).
An algebra is a place where wrapped things can be evaluated: . The unit law says that evaluating a trivially wrapped element returns it; the associativity law says that evaluating the inner layers first and then the outer layer is the same as flattening first and then evaluating. For lists this is the generalised associative law of a monoid, and that is the whole story: the algebras of the list monad are exactly the monoids.
Monads, algebras and Kleisli arrows
Throughout, is a category and composition is written (" after "). Recall from natural transformations how to whisker: for and a functor , and .
Definition— monad
A monad on is a triple consisting of a functor and natural transformations (the unit) and (the multiplication) such that for every object
The laws are the commutativity of the following diagrams (associativity on the left, the two unit laws on the right).
Definition— algebra for a monad (Eilenberg–Moore algebra)
A -algebra is a pair of an object and a morphism (the structure map) such that
A morphism of algebras is a morphism with . Algebras and their morphisms form the Eilenberg–Moore category , with a forgetful functor . For every , the pair is an algebra — the free algebra on — and , defines a functor .
Definition— Kleisli category
The Kleisli category has the same objects as , and a morphism in is a morphism in (a Kleisli arrow). The Kleisli composite of and and the Kleisli identity are
There are functors , , for , and , , for .
Think of a Kleisli arrow as a "generalised function" from to : many-valued (list or power-set monad), partial (maybe monad), random (distribution monad), or effectful (in programming).
Definition— monadic functor
A functor is monadic if it has a left adjoint and the comparison functor (constructed below) is an equivalence of categories. Informally: the objects of are exactly "objects of with -algebra structure".
Examples and non-examples
Example— The list (free monoid) monad on Set
, the set of finite sequences; applies to each entry; ; concatenates a list of lists. Associativity says that flattening a list of lists of lists inside-first or outside-first gives the same list; the unit laws say and . All three are proved by induction on the outer list (and in Lean below). This monad comes from the adjunction between the free-monoid functor and the forgetful functor .
Example— The power-set monad on Set
; (direct image); ; . Associativity is for ; the unit laws are and .
An algebra is a complete join-semilattice: define . Conversely, a poset with all joins gives . (Sketch: the associativity law gives whenever , from which is a partial order and is the least upper bound of ; the unit law gives .) Algebra morphisms are maps preserving all joins. A Kleisli arrow is a relation from to , and Kleisli composition is composition of relations: .
Example— The maybe (exception) monad on Set
; ; collapses the two copies of in . An algebra must be the identity on (unit law), and the associativity law then holds automatically, so an algebra is just a base point : is the category of pointed sets. (The empty set has no algebra structure.) A Kleisli arrow is a partial function, and is the category of sets and partial functions.
Example— Closure operators: monads on a poset
Regard a poset as a category with a unique arrow when . A functor is a monotone map; says ; says ; all diagrams commute automatically. So a monad on a poset is exactly a closure operator (and then ). Its algebras are the elements with , i.e. the closed elements. Topological closure on , the subgroup generated by a subset, the convex hull and the radical of an ideal are all examples; so is for any Galois connection.
Example— The distribution monad
is the set of finitely supported probability distributions on ; pushes distributions forward; (point mass); averages a distribution of distributions. A Kleisli arrow is a Markov kernel — for finite , a stochastic matrix — and Kleisli composition is the Chapman–Kolmogorov formula: the product of stochastic matrices (see Markov chains). Expectation is an algebra; its associativity law is the tower property for finitely supported random variables. More generally every convex subset of a real vector space is a -algebra via barycentres — but so is every join-semilattice (send a distribution to the join of its support), so "-algebra" (also called a convex space) is broader than "convex set".
Example— Monads from adjunctions
Every adjunction gives a monad (proved below). The free group functor gives the monad sending to the set of reduced words in . For a ring , extension of scalars gives the monad on , whose algebras are -modules. The discrete-space functor gives the identity monad, since ; this will show that is not monadic over .
Example— A non-example: the wrong multiplication
Keep and but flatten by intersection, . The associativity law and the left unit law hold (with the convention ), but the right unit law fails: . Nor is this natural: for a non-injective , can be smaller than . (The experiment below finds the unit-law counterexample by search.)
Adjunctions, Kleisli and Eilenberg–Moore
Let be left adjoint to , with unit and counit satisfying the triangle identities and .
Theorem— Theorem 1 (every adjunction induces a monad; Huber)
is a monad on : its multiplication has components .
Proof
is natural because is. Associativity. Naturality of applied to the morphism gives . Applying :
Left unit. by the second triangle identity at . Right unit. by the first triangle identity.
∎
Now go the other way: start with a monad and look for adjunctions that induce it.
Theorem— Theorem 2 (the Kleisli adjunction; Kleisli 1965)
is a category, and are functors, , and the monad induced by this adjunction is .
Proof
Identities. For : by the right unit law; and by naturality of and the left unit law.
Associativity. For , , :
using associativity of and then naturality of at ().
Functors. by naturality of and the unit law; by the same two laws; identities are preserved by the unit laws.
Adjunction. , and this identity bijection is natural in and (a direct check with the formulas above). Its unit is (the arrow corresponding to ) and its counit at is the Kleisli arrow .
The induced monad. and , so ; the unit is ; and .
∎
Theorem— Theorem 3 (the Eilenberg–Moore adjunction; Eilenberg and Moore 1965)
, and the monad induced by this adjunction is .
Proof
First, is an algebra: the unit and associativity laws for it are the left unit law and the associativity law of the monad; and is an algebra map by naturality of . For an algebra define
is an algebra map: (naturality of , then the associativity law of ). The maps are inverse: (naturality of , unit law of ), and for an algebra map , (algebra map, right unit law). Naturality in and is immediate. The unit of the adjunction is , and the counit at is , an algebra map . Finally and .
∎
So every monad arises from an adjunction, generally from many. The two constructions are the extreme ones.
Theorem— Theorem 4 (comparison functors)
Let induce . Then , , , is a functor with and . Likewise , , for , is a functor with and . Each is the unique functor with these properties; hence the Kleisli adjunction is initial and the Eilenberg–Moore adjunction terminal among adjunctions inducing .
Proof
We check that is well defined. Unit law: (triangle identity). Associativity law: (naturality of at ). For , , so is an algebra map. Clearly , and , with . The checks for are similar (functoriality uses the triangle identity and naturality of ). For uniqueness, see Mac Lane, Categories for the Working Mathematician, VI.3 Theorem 1 and VI.5 Theorem 3.
∎
Applied to the Eilenberg–Moore adjunction, the functor sends to the free algebra and to . It is fully faithful: on morphisms it is the bijection of Theorem 3. So the Kleisli category is (equivalent to) the full subcategory of free algebras. Whether it is all of depends on the monad.
Theorem— Proposition 5 (Kleisli and Eilenberg–Moore can differ)
For the maybe monad, is an equivalence. For the list monad, and are not equivalent by any functor.
Proof
Maybe. Every pointed set is isomorphic to the free algebra on , so the fully faithful functor is essentially surjective, hence an equivalence: sets and partial functions are equivalent to pointed sets.
List. An equivalence of categories preserves endomorphism monoids up to isomorphism (it is fully faithful). In , the monoid has exactly two endomorphisms ( and ). In , the endomorphisms of are the functions : one if , infinitely many otherwise. So no object of can correspond to .
∎
Beck's monadicity theorem
Which adjunctions are "the Eilenberg–Moore one" up to equivalence? A parallel pair in is -split if has a split coequaliser in : a morphism with , together with and such that , and . (Split coequalisers are coequalisers, and are preserved by every functor.)
Theorem— Beck's monadicity theorem (Beck 1967)
A functor is monadic if and only if
- has a left adjoint,
- reflects isomorphisms, and
- has coequalisers of -split pairs, and preserves them.
The proof constructs a left adjoint to the comparison functor by sending an algebra to the coequaliser in of the -split pair (every algebra is canonically a coequaliser of free algebras), and then shows that unit and counit are isomorphisms. Full proofs: Mac Lane, CWM VI.7; Riehl, Category Theory in Context, Theorem 5.5.1. Mathlib proves it (cited in the formal section).
Example— Groups are monadic over Set
has the free-group left adjoint, and a bijective homomorphism is an isomorphism, so reflects isomorphisms. Sketch of condition 3. Let be homomorphisms with a split coequaliser of sets, with as above. If , then with and we get , and . So the relation is the composite of the relation with its converse. is the image of the homomorphism , hence a subgroup of , and composites and converses of subgroups of are again subgroups. So is a congruence on , carries a unique group structure making a homomorphism, and one checks that is then a coequaliser of in , preserved by . By Beck, .
The same argument (or Linton's theorem that every variety of universal algebras is monadic) applies to , , and . The category of compact Hausdorff spaces is monadic over too, via the ultrafilter monad (Manes 1969) — although it is not a variety of finitary algebras.
Example— Topological spaces are not monadic over Set
The left adjoint of is the discrete-space functor, so the induced monad is the identity monad, whose algebras are just sets (the unit law forces ). The comparison functor is itself, and it is not an equivalence: the identity map from a two-point discrete space to a two-point indiscrete space is continuous and bijective but not a homeomorphism, so does not reflect isomorphisms (condition 2 fails). The same argument shows that posets are not monadic over .
Example— Monads in programming (Moggi)
Model a programming language whose programs may have effects by a monad on a category of types: a program from to is a Kleisli arrow , and running programs in sequence is Kleisli composition. Exceptions: . Nondeterminism: or . Global state: . Output to a log: for a monoid . Probabilistic choice: . Moggi (1989, 1991) made this precise; Wadler brought it into functional programming, and in Haskell return is and >>= (bind) is Kleisli extension: extends to . A monad can equivalently be given as a Kleisli triple satisfying , and — which are exactly the three Kleisli category laws, i.e. Haskell's "monad laws". The monads came from algebra and topology; programming found them afterwards.
Monads in Lean
The Lean module works at two levels. First, with plain functions on types, it checks the monad and Kleisli laws for lists by induction, and proves in full that list-algebras are monoids:
/-- **The list monad satisfies the monad laws.** Here `T X = List X`, the unit `η` sends `x` to
the one-element list `[x]`, and the multiplication `μ` flattens a list of lists.
1. associativity `μ ∘ Tμ = μ ∘ μT`: flattening the inner lists first, or the outer list first,
gives the same list;
2. left unit `μ ∘ ηT = id`: flattening `[xs]` gives `xs`;
3. right unit `μ ∘ Tη = id`: flattening `[[x₁], …, [xₙ]]` gives `[x₁, …, xₙ]`.
Each law is proved by induction on the outer list. -/
theorem list_monad_laws {α : Type u} :
(∀ xsss : List (List (List α)), (xsss.map List.flatten).flatten = xsss.flatten.flatten) ∧
(∀ xs : List α, [xs].flatten = xs) ∧
(∀ xs : List α, (xs.map fun x => [x]).flatten = xs) := by
refine ⟨fun xsss => ?_, fun xs => by simp, fun xs => ?_⟩
· induction xsss with
| nil => rfl
| cons xss rest ih =>
simp only [List.map_cons, List.flatten_cons, List.flatten_append, ih]
· induction xs with
| nil => rfl
| cons x rest ih => simp only [List.map_cons, List.flatten_cons, ih, List.singleton_append]/-- **Kleisli composition for the list monad.** A Kleisli arrow `α → List β` is a
"many-valued function". To compose `f : α → List β` with `g : β → List γ`, apply `g` to every
output of `f` and flatten: in categorical notation `μ ∘ T g ∘ f`. -/
def listKleisli {α : Type u} {β : Type v} {γ : Type w} (f : α → List β) (g : β → List γ) :
α → List γ :=
fun a => (f a).flatMap g/-- **The Kleisli category of the list monad is a category.** Kleisli composition is associative,
and `η = fun a => [a]` is a two-sided identity. Associativity is core Lean's `List.flatMap_assoc`;
the identity laws are short computations. -/
theorem list_kleisli_laws {α β γ δ : Type u} (f : α → List β) (g : β → List γ)
(h : γ → List δ) :
listKleisli (listKleisli f g) h = listKleisli f (listKleisli g h) ∧
listKleisli (fun a => [a]) f = f ∧
listKleisli f (fun b => [b]) = f := by
refine ⟨?_, ?_, ?_⟩
· funext a
exact List.flatMap_assoc
· funext a
simp [listKleisli]
· funext a
simp [listKleisli]A list algebra is an evaluation map with the two laws. The key lemma is the human proof's first step: an algebra is determined by evaluating two-element lists.
/-- An **algebra for the list monad** on a type `X`: an evaluation map `act : List X → X` such
that evaluating a one-element list returns its element (`act ∘ η = id`), and evaluating a list of
lists "inside first" or "flatten first" agrees (`act ∘ T act = act ∘ μ`). -/
structure ListAlgebra (X : Type u) where
/-- Evaluate a list of elements to a single element. -/
act : List X → X
/-- The unit law `act [x] = x`. -/
unit : ∀ x : X, act [x] = x
/-- The associativity law: evaluate the inner lists first, or flatten first. -/
assoc : ∀ xss : List (List X), act (xss.map act) = act xss.flatten/-- In a list algebra, evaluating `x :: xs` is the same as evaluating the two-element list
`[x, act xs]`:
`act [x, act xs] = act [act [x], act xs] = act (flatten [[x], xs]) = act (x :: xs)`,
using the unit law and then the associativity law. -/
theorem act_cons (A : ListAlgebra X) (x : X) (xs : List X) :
A.act (x :: xs) = A.act [x, A.act xs] := by
calc A.act (x :: xs) = A.act ([[x], xs].flatten) := by simp
_ = A.act ([[x], xs].map A.act) := (A.assoc [[x], xs]).symm
_ = A.act [x, A.act xs] := by simp [A.unit]/-- **Every list algebra is a monoid.** Multiply by evaluating two-element lists,
`x * y = act [x, y]`, and take `1 = act []`. The monoid laws follow from the algebra laws:
for example `(x * y) * z = act [act [x, y], act [z]] = act [x, y, z]`, and likewise
`x * (y * z) = act [x, y, z]`. -/
@[instance_reducible]
def toMonoid (A : ListAlgebra X) : Monoid X where
mul x y := A.act [x, y]
one := A.act []
mul_assoc x y z := by
show A.act [A.act [x, y], z] = A.act [x, A.act [y, z]]
have h₁ : A.act [A.act [x, y], z] = A.act [x, y, z] := by
calc A.act [A.act [x, y], z] = A.act ([[x, y], [z]].map A.act) := by simp [A.unit]
_ = A.act [x, y, z] := by rw [A.assoc]; simp
have h₂ : A.act [x, A.act [y, z]] = A.act [x, y, z] := by
calc A.act [x, A.act [y, z]] = A.act ([[x], [y, z]].map A.act) := by simp [A.unit]
_ = A.act [x, y, z] := by rw [A.assoc]; simp
rw [h₁, h₂]
one_mul x := by
show A.act [A.act [], x] = x
calc A.act [A.act [], x] = A.act ([[], [x]].map A.act) := by simp [A.unit]
_ = x := by rw [A.assoc]; simp [A.unit]
mul_one x := by
show A.act [x, A.act []] = x
calc A.act [x, A.act []] = A.act ([[x], []].map A.act) := by simp [A.unit]
_ = x := by rw [A.assoc]; simp [A.unit]/-- **Every monoid is a list algebra**: evaluate a list by multiplying it out, `act = List.prod`.
The unit law is `List.prod_singleton` and the associativity law is core Lean's
`List.prod_flatten` (multiplying blockwise, then multiplying the results, is multiplying
everything). -/
def ofMonoid (M : Type u) [Monoid M] : ListAlgebra M where
act := List.prod
unit _ := List.prod_singleton
assoc _ := List.prod_flatten.symm/-- **A list algebra is determined by its binary multiplication and unit.** Evaluating any list
is multiplying it out in the monoid `A.toMonoid`. Proof by induction on the list, using
`act_cons` at each step. -/
theorem act_eq_prod (A : ListAlgebra X) (xs : List X) :
A.act xs = @List.prod X A.toMonoid.toMul A.toMonoid.toOne xs := by
induction xs with
| nil => rfl
| cons x xs ih =>
rw [act_cons, ih]
rflThe theorem "algebras for the list monad are exactly monoids" is then an explicit bijection between the two kinds of structure on a type:
/-- **Algebras for the list monad are exactly monoids.** On any type `X`, list-algebra structures
correspond bijectively to monoid structures: send an algebra to its binary multiplication, and a
monoid to `List.prod`. Going around one way uses `act_eq_prod`; going around the other way only
has to check that the multiplications agree (`Monoid.ext`), and `List.prod [x, y] = x * (y * 1)`. -/
def listAlgebraEquivMonoid (X : Type u) : ListAlgebra X ≃ Monoid X where
toFun A := A.toMonoid
invFun m := @ListAlgebra.ofMonoid X m
left_inv A := by
ext xs
exact (A.act_eq_prod xs).symm
right_inv m := by
let _ := m
apply Monoid.ext
funext x y
show List.prod [x, y] = x * y
simp/-- **Algebras for the maybe monad are exactly pointed sets.** An `Option`-algebra on `X` is
determined by where it sends `none`, and every choice of base point gives an algebra (with
`act = fun o => o.getD a`; the associativity law then holds automatically, by checking the three
shapes `none`, `some none`, `some (some x)`). -/
def optionAlgebraEquiv (X : Type u) : OptionAlgebra X ≃ X where
toFun A := A.act none
invFun a :=
{ act := fun o => o.getD a
unit := fun _ => rfl
assoc := by
intro oo
rcases oo with _ | _ | x <;> rfl }
left_inv A := by
rcases A with ⟨act, unit, assoc⟩
have h : (fun o => o.getD (act none)) = act := by
funext o
cases o with
| none => rfl
| some x => exact (unit x).symm
simp only [OptionAlgebra.mk.injEq]
exact h
right_inv _ := rflSecond, Mathlib has the general theory: CategoryTheory.Monad, the Eilenberg–Moore category Monad.Algebra, Kleisli, Adjunction.toMonad, the comparison functor and Beck's theorem. The following are one-line applications of Mathlib, recording Theorems 1–4 and Beck's theorem in Mathlib's language (note that Mathlib writes composition in diagrammatic order: F ⋙ G is ):
/-- **Every adjunction induces a monad.** For `F ⊣ G` with unit `η` and counit `ε`, the monad
`h.toMonad` (Mathlib's `Adjunction.toMonad`) has underlying functor `G ∘ F` (written `F ⋙ G` in
diagrammatic order), unit `η`, and multiplication `μ_X = G(ε_{F X})`. -/
theorem adjunction_monad {F : C ⥤ D} {G : D ⥤ C} (h : F ⊣ G) (X : C) :
(h.toMonad : C ⥤ C) = F ⋙ G ∧ h.toMonad.η = h.unit ∧
h.toMonad.μ.app X = G.map (h.counit.app (F.obj X)) :=
⟨rfl, rfl, rfl⟩/-- **The Kleisli adjunction induces the monad.** For a monad `T`, the free functor
`C ⥤ Kleisli T` followed by the forgetful functor `Kleisli T ⥤ C` is isomorphic to `T`
(Mathlib's `Kleisli.Adjunction.toKleisliCompFromKleisliIsoSelf`). -/
theorem kleisli_induces (T : Monad C) :
Nonempty (Kleisli.Adjunction.toKleisli T ⋙ Kleisli.Adjunction.fromKleisli T ≅
(T : C ⥤ C)) :=
⟨Kleisli.Adjunction.toKleisliCompFromKleisliIsoSelf T⟩/-- **The Eilenberg–Moore adjunction induces the monad.** The free-algebra functor
`T.free : C ⥤ T.Algebra` is left adjoint to the forgetful functor (`T.adj`), and the monad of
this adjunction is isomorphic to `T` (Mathlib's `Adjunction.adjToMonadIso`). -/
theorem eilenberg_moore_induces (T : Monad C) : Nonempty (T.adj.toMonad ≅ T) :=
⟨Adjunction.adjToMonadIso T⟩/-- **The comparison functor.** For any adjunction `F ⊣ G` with monad `T = G F`, the functor
`K = Monad.comparison h : D ⥤ T.Algebra` sends `B` to the algebra `(G B, G ε_B)`. It is
compatible with both adjunctions: `K ∘ F` is the free-algebra functor (`Monad.left_comparison`)
and the forgetful functor after `K` is `G` (`Monad.comparisonForget`). -/
theorem comparison {F : C ⥤ D} {G : D ⥤ C} (h : F ⊣ G) :
F ⋙ Monad.comparison h = h.toMonad.free ∧
Nonempty (Monad.comparison h ⋙ h.toMonad.forget ≅ G) :=
⟨Monad.left_comparison h, ⟨Monad.comparisonForget h⟩⟩/-- **Beck's monadicity theorem** (one of Mathlib's versions,
`Monad.monadicOfHasPreservesReflectsGSplitCoequalizers`): if `G` has a left adjoint, `D` has
coequalisers of `G`-split pairs, and `G` preserves and reflects them, then `G` is monadic — the
comparison functor `D ⥤ T.Algebra` is an equivalence. -/
theorem beck {F : C ⥤ D} {G : D ⥤ C} (h : F ⊣ G) [Monad.HasCoequalizerOfIsSplitPair G]
[Monad.PreservesColimitOfIsSplitPair G] [Monad.ReflectsColimitOfIsSplitPair G] :
Nonempty (MonadicRightAdjoint G) :=
⟨Monad.monadicOfHasPreservesReflectsGSplitCoequalizers h⟩/-- **A monad is a monoid in the category of endofunctors.** The category of monads on `C` is
equivalent to the category of monoid objects in `(C ⥤ C, ∘, 𝟭)` (Mathlib's
`Monad.monadMonEquiv`). -/
theorem monads_are_monoids (C : Type u) [Category.{v} C] :
Nonempty (Monad C ≌ Mon (C ⥤ C)) :=
⟨Monad.monadMonEquiv C⟩What is and is not formalised. Independently proved here: the list monad and Kleisli laws, the equivalence between list-algebras and monoids, and the classification of maybe-algebras. Taken from Mathlib: the adjunction–monad correspondence, both canonical adjunctions, the comparison functor, Beck's theorem and monads as monoids. Not formalised: monadicity of , the non-monadicity of , and Proposition 5. The list-algebra statements are about functions List X → X; Mathlib's ofTypeMonad List packages the same monad as a CategoryTheory.Monad (Type u), and connecting the two is a routine but fiddly exercise in unfolding Mathlib's morphisms of types.
Searching for algebras; composing kernels
The first experiment searches every candidate structure map on small sets. It finds that maybe-algebras are base points, that power-set algebras on points are the chains with , and that among the 243 unital binary operations on a 3-element set, the list-algebra laws hold exactly for the 33 associative ones — the monoids. It also catches the "intersection monad" breaking a unit law.
"""Counting Eilenberg-Moore algebras on small finite sets.
An algebra for a monad T on a set X is a map a : T X -> X satisfying the unit
law a(eta x) = x and the associativity law a(T a) = a(mu). For three monads on
Set we search *every* candidate map on small sets and count the algebras:
* maybe monad (T X = X + {None}): algebras should be base points;
* power-set monad (T X = subsets of X, mu = union): algebras should be
complete join-semilattices, a(S) = sup S;
* list monad, restricted to unital binary operations on a 3-element set: the
algebra laws (checked on a finite slice of nested lists) should hold exactly
for the associative ones, i.e. for the monoids.
Finally a "monad" with the wrong multiplication (intersection instead of
union) is caught breaking a unit law. All searches are exhaustive over the
stated finite ranges; they illustrate, but do not prove, the theorems.
"""
import itertools
def maybe_algebras(n):
"""All maybe-algebras on {0..n-1}, by searching every map Option X -> X.
A map is a tuple act of length n + 1; index n stands for None. Returns the
maps satisfying the unit law and the associativity law.
"""
def ev(act, o):
return act[n if o is None else o]
# oo ranges over the shapes None, Some None and Some (Some x).
shapes = [None, ("some", None)] + [("some", x) for x in range(n)]
found = []
for act in itertools.product(range(n), repeat=n + 1):
if any(act[x] != x for x in range(n)):
continue
ok = True
for oo in shapes:
inner = None if oo is None else oo[1]
mapped = None if oo is None else ev(act, inner)
ok = ok and ev(act, mapped) == ev(act, inner)
if ok:
found.append(act)
return found
def subsets(n):
"""Subsets of {0..n-1} as bitmasks."""
return list(range(1 << n))
def members(mask):
"""Elements of the subset encoded by a bitmask."""
return [i for i in range(mask.bit_length()) if mask >> i & 1]
def is_powerset_algebra(act, n):
"""Check unit and associativity laws for a : P(X) -> X (dict on masks)."""
if any(act[1 << x] != x for x in range(n)):
return False
subs = subsets(n)
for family in range(1 << len(subs)):
chosen = [subs[i] for i in members(family)]
union = 0
image = 0
for s in chosen:
union |= s
image |= 1 << act[s]
if act[image] != act[union]:
return False
return True
def powerset_algebras(n):
"""All power-set algebras on {0..n-1}, found by exhaustive search."""
free = [s for s in subsets(n) if bin(s).count("1") != 1]
found = []
for values in itertools.product(range(n), repeat=len(free)):
act = {1 << x: x for x in range(n)}
act.update(zip(free, values))
if is_powerset_algebra(act, n):
found.append(act)
return found
def order_of(act, n):
"""The relation x <= y iff a({x, y}) = y, as a set of pairs."""
return {(x, y) for x in range(n) for y in range(n)
if act[(1 << x) | (1 << y)] == y}
def is_least_upper_bound_map(act, rel, n):
"""True if a(S) is the least upper bound of S in rel, for every S."""
for mask in subsets(n):
s = members(mask)
uppers = [y for y in range(n) if all((x, y) in rel for x in s)]
if act[mask] not in uppers:
return False
if any((act[mask], v) not in rel for v in uppers):
return False
return True
def is_chain(rel, n):
"""True if rel is a total order on {0..n-1}."""
total = all((x, y) in rel or (y, x) in rel for x in range(n)
for y in range(n))
anti = all(x == y or not ((x, y) in rel and (y, x) in rel)
for x in range(n) for y in range(n))
return total and anti
def unital_magmas(n):
"""All binary operations on {0..n-1} (as tuples of rows) with a unit."""
out = []
for table in itertools.product(range(n), repeat=n * n):
rows = tuple(table[i * n:(i + 1) * n] for i in range(n))
units = [e for e in range(n)
if all(rows[e][x] == x == rows[x][e] for x in range(n))]
if units:
out.append((rows, units[0]))
return out
def fold(rows, unit, xs):
"""Evaluate a list by right-folding: x1 * (x2 * (... * (xn * e)))."""
value = unit
for x in reversed(xs):
value = rows[x][value]
return value
def list_algebra_law_holds(rows, unit, n, max_len=2):
"""Check fold(map fold xss) = fold(flatten xss) on a finite slice."""
inner = [list(t) for k in range(max_len + 1)
for t in itertools.product(range(n), repeat=k)]
for k in range(max_len + 1):
for xss in itertools.product(inner, repeat=k):
lhs = fold(rows, unit, [fold(rows, unit, xs) for xs in xss])
rhs = fold(rows, unit, [x for xs in xss for x in xs])
if lhs != rhs:
return False
return True
def is_associative(rows, n):
"""True if the operation given by rows is associative."""
return all(rows[rows[a][b]][c] == rows[a][rows[b][c]]
for a in range(n) for b in range(n) for c in range(n))
def intersection_unit_counterexample(n):
"""Find a nonempty S with mu(P eta S) != S when mu is intersection."""
for mask in subsets(n)[1:]:
singletons = [1 << x for x in members(mask)]
meet = (1 << n) - 1
for s in singletons:
meet &= s
if meet != mask:
return mask, meet
return None
def main():
print("Maybe monad: algebras on an n-element set")
for n in range(4):
count = len(maybe_algebras(n))
print(f" n = {n}: {count} algebra(s) among {n ** (n + 1)} maps"
" Option X -> X")
print("Power-set monad: algebras a : P(X) -> X found by full search")
for n in range(1, 4):
algebras = powerset_algebras(n)
sups = sum(is_chain(order_of(a, n), n)
and is_least_upper_bound_map(a, order_of(a, n), n)
for a in algebras)
print(f" n = {n}: {len(algebras)} algebra(s); in {sups} of them "
"a(S) = sup S in a chain")
magmas = unital_magmas(3)
algebra = [m for m in magmas if list_algebra_law_holds(*m, 3)]
assoc = [m for m in magmas if is_associative(m[0], 3)]
print("List monad: unital operations on {0, 1, 2}")
print(f" unital operations: {len(magmas)}")
print(f" folds obeying the list-algebra laws: {len(algebra)}")
print(f" associative operations (monoids): {len(assoc)}")
print(f" the two collections coincide: {algebra == assoc}")
mask, meet = intersection_unit_counterexample(3)
print("Fake monad: P with mu = intersection instead of union")
print(f" S = {members(mask)}: mu(P eta S) = {members(meet)} != S, "
"so the right unit law fails")
print()
print("Epistemic status: exhaustive search over the finite ranges shown "
"(sets\nwith at most 3 elements; list-algebra laws checked on nested "
"lists of\nlength at most 2). It agrees with the theorems in the "
"lesson but proves\nthem only for these cases; the proofs (and the "
"Lean file) cover all sets.")
if __name__ == "__main__":
main()The second experiment works in the Kleisli category of the distribution monad. It composes Markov kernels using only and (push forward, then average) and compares with matrix multiplication, in exact rational arithmetic; it also checks the tower property, which is the associativity law of the expectation algebra.
"""The distribution monad: Kleisli arrows are Markov kernels.
D X is the set of finitely supported probability distributions on X; the unit
sends x to the point mass at x and the multiplication averages a distribution
of distributions. A Kleisli arrow X -> D Y is a Markov kernel (a stochastic
matrix), and the experiment computes Kleisli composition *from the monad
structure* (push forward, then flatten) and compares it with the product of
stochastic matrices - the Chapman-Kolmogorov equation. It also checks the
Kleisli identity and associativity laws, and that expectation E : D(R) -> R is
an Eilenberg-Moore algebra whose associativity law is the tower property of
conditional expectation. Exact rational arithmetic; random kernels are seeded.
"""
import random
from fractions import Fraction
def unit(x):
"""eta: the point mass at x, as a dict {outcome: probability}."""
return {x: Fraction(1)}
def flatten(dist_of_dists):
"""mu: average a list of (probability, distribution) pairs."""
out = {}
for p, dist in dist_of_dists:
for y, q in dist.items():
out[y] = out.get(y, Fraction(0)) + p * q
return {y: q for y, q in out.items() if q != 0}
def push(dist, kernel):
"""D k: apply a kernel to every outcome, giving a distribution of dists."""
return [(p, kernel(x)) for x, p in dist.items()]
def kleisli(f, g):
"""Kleisli composite x |-> mu(D g (f x)) of kernels f, then g."""
return lambda x: flatten(push(f(x), g))
def kernel_from_matrix(matrix):
"""The kernel i |-> row i of a stochastic matrix."""
return lambda i: {j: p for j, p in enumerate(matrix[i]) if p != 0}
def matrix_from_kernel(kernel, rows, cols):
"""The stochastic matrix of a kernel on {0..rows-1} -> {0..cols-1}."""
return [[kernel(i).get(j, Fraction(0)) for j in range(cols)]
for i in range(rows)]
def mat_mul(a, b):
"""Product of matrices with Fraction entries."""
return [[sum((a[i][k] * b[k][j] for k in range(len(b))), Fraction(0))
for j in range(len(b[0]))] for i in range(len(a))]
def random_stochastic(rows, cols, rng):
"""A random stochastic matrix with small rational entries."""
matrix = []
for _ in range(rows):
weights = [rng.randint(0, 4) for _ in range(cols)]
if sum(weights) == 0:
weights[rng.randrange(cols)] = 1
total = sum(weights)
matrix.append([Fraction(w, total) for w in weights])
return matrix
def expectation(dist):
"""The algebra map E : D(Q) -> Q."""
return sum((p * x for x, p in dist.items()), Fraction(0))
def tower_property_holds(dist_of_dists):
"""E(D E (P)) == E(mu P): the algebra associativity law for E."""
outer = {}
for p, dist in dist_of_dists:
m = expectation(dist)
outer[m] = outer.get(m, Fraction(0)) + p
return expectation(outer) == expectation(flatten(dist_of_dists))
def random_mixture(size, rng):
"""A random distribution (list of weighted pairs) of distributions."""
out = []
for p in random_stochastic(1, size, rng)[0]:
dist = {}
for q in random_stochastic(1, 3, rng)[0]:
v = Fraction(rng.randint(-9, 9))
dist[v] = dist.get(v, Fraction(0)) + q
out.append((p, dist))
return out
def run_trials(trials, rng):
"""Count failures of each law on random kernels between small sets."""
fails = {"matrix": 0, "assoc": 0, "unit": 0, "tower": 0}
for _ in range(trials):
a, b, c, d = (rng.randint(1, 4) for _ in range(4))
m1, m2, m3 = (random_stochastic(a, b, rng),
random_stochastic(b, c, rng),
random_stochastic(c, d, rng))
f, g, h = map(kernel_from_matrix, (m1, m2, m3))
if matrix_from_kernel(kleisli(f, g), a, c) != mat_mul(m1, m2):
fails["matrix"] += 1
left = matrix_from_kernel(kleisli(kleisli(f, g), h), a, d)
right = matrix_from_kernel(kleisli(f, kleisli(g, h)), a, d)
fails["assoc"] += left != right
ident = (matrix_from_kernel(kleisli(unit, f), a, b) == m1
== matrix_from_kernel(kleisli(f, unit), a, b))
fails["unit"] += not ident
fails["tower"] += not tower_property_holds(random_mixture(b, rng))
return fails
def main():
rain = [[Fraction(7, 10), Fraction(3, 10)],
[Fraction(2, 5), Fraction(3, 5)]]
weather = kernel_from_matrix(rain)
two_days = matrix_from_kernel(kleisli(weather, weather), 2, 2)
print("A two-state weather chain (0 = dry, 1 = rainy) as a Kleisli arrow")
print(f" one day: {[[str(p) for p in row] for row in rain]}")
print(f" two days via mu . D k . k: "
f"{[[str(p) for p in row] for row in two_days]}")
print(f" equals the matrix square: {two_days == mat_mul(rain, rain)}")
rng = random.Random(2024)
fails = run_trials(200, rng)
print("200 seeded random kernels between sets of size 1 to 4:")
print(f" Kleisli composite != matrix product: {fails['matrix']}")
print(f" associativity failures: {fails['assoc']}")
print(f" identity-law failures (eta = Dirac): {fails['unit']}")
print(f" tower-property failures for E: {fails['tower']}")
print()
print("Epistemic status: exact rational arithmetic, so each check is a "
"true\nstatement about the kernels tested; but 200 random cases "
"are evidence,\nnot proof. The general facts (Kleisli composition "
"is matrix product,\nE is a D-algebra) follow from linearity, as "
"the lesson explains.")
if __name__ == "__main__":
main()Exercises
1. Monads
Warm-upHumanThe maybe monad on has , , and collapsing the two copies of . What is a -algebra ?
2. Monads
CoreHumanHow many algebra structures for the power-set monad are there on the three-element set ?
3. Monads
Warm-upHumanIn the Kleisli category of the list monad, let and (both from to ). What is ?
4. Monads
CoreHumanLet with , unit and counit . The induced monad is on . What is its multiplication ?
5. Monads
CoreHumanIn proving that is a right identity in the Kleisli category, we write, for ,
Which two facts justify the second and third equalities, in order?
6. Monads
StretchHumanFor the list monad on , . Which argument shows that the Kleisli category is not equivalent to ?
7. Monads
CoreHumanWhich forgetful functors to are monadic? (Choose all that apply.)
8. Monads
CoreFormalIn the Lean file, a list algebra on X has act : List X → X. Which field states the associativity law ?
9. Monads
StretchComputationalWrite kleisli(f, g) returning the Kleisli composite (first f, then g) for the list monad: f and g are Python functions returning lists, and the composite applies g to every output of f and concatenates. Also write unit(x) returning [x]. The tests check the Kleisli category laws on examples.
Common misconceptions
Misconception
"Monads come from programming." They arose in algebraic topology and homological algebra: Godement's construction fondamentale (1958) for sheaf cohomology, Huber's observation (1961) that adjunctions yield them, and the work of Eilenberg, Moore, Kleisli and Beck in the 1960s, when they were called standard constructions or triples. Moggi applied them to programming semantics in 1989.
Misconception
"The Kleisli and Eilenberg–Moore categories of a monad are always equivalent." The Kleisli category is the full subcategory of free algebras. It is equivalent to the whole Eilenberg–Moore category only when every algebra is (isomorphic to) a free one — true for the maybe monad, false for the list monad, where the monoid is not free and has an endomorphism monoid of size 2 that no Kleisli object has.
Misconception
"A monad is just a functor with some maps and ." The maps must be natural and satisfy the associativity and both unit laws. The power-set functor with and satisfies the associativity law and the left unit law, yet it is not a monad: the right unit law fails, and this is not even natural.
Misconception
"Every algebra structure is unique / every forgetful functor with a left adjoint is monadic." A set can carry many algebra structures (six power-set algebras on three points). And having a left adjoint is not enough: has one but is not monadic, because it does not reflect isomorphisms.
Viewpoints
Topology— Compact spaces as algebras; sheaf resolutions
The ultrafilter monad on sends to the set of ultrafilters on (for discrete , the Stone–Čech compactification), with the principal ultrafilters. Manes' theorem (1969): the algebras for are exactly the compact Hausdorff spaces, the structure map sending each ultrafilter to its unique limit, and algebra morphisms are continuous maps. Compactness says every ultrafilter has a limit, Hausdorffness that it is unique; the algebra laws encode the topology. So a purely algebraic notion recovers a central class of spaces (see point-set topology).
Godement's original monad lives on sheaves: if is with the discrete topology and the identity map, the adjunction gives the monad of discontinuous sections. Iterating with the face maps built from and yields the flabby Godement resolution , which computes sheaf cohomology (see sheaves).
Algebra— Varieties, theories and descent
For a variety of algebras (groups, rings, lattices, -modules, …) the free-algebra adjunction is monadic, and the monad is finitary (determined by its values on finite sets). Conversely, finitary monads on correspond to Lawvere's algebraic theories (Lawvere 1963; Linton 1966): a monad is a presentation-free description of an equational theory, being the set of terms in variables modulo the equations, the variables and substitution. For a monoid object in a monoidal category (a ring in , an algebra in ), is a monad whose algebras are the -modules.
Dually, Beck's theorem for comonads is the abstract form of Grothendieck's descent. For a faithfully flat homomorphism of commutative rings , extension of scalars is comonadic, and coalgebras for the induced comonad are -modules with descent data (Bénabou–Roubaud 1970). This is the algebraic heart of gluing modules and quasi-coherent sheaves along faithfully flat covers in algebraic geometry.
Probability— Kleisli arrows are Markov kernels
For the finite distribution monad , Kleisli arrows are Markov kernels and Kleisli composition is the Chapman–Kolmogorov equation ; the Kleisli category of on finite sets is the category of stochastic matrices. A Markov chain with transition kernel is an endomorphism in this category, and its -step behaviour is the -fold Kleisli power (see Markov chains). Expectation is an algebra , and its associativity law is the tower property. Lawvere (1962) and Giry (1982) extended this to the Giry monad on measurable spaces, sending to the space of probability measures on it; its Kleisli arrows are measurable Markov kernels, and this is now the basis of categorical probability and of the semantics of probabilistic programming (see measure-theoretic probability).
Computation— Effects and the monad laws
In Moggi's semantics a type of computations returning is , return is and sequencing (>>=, do-notation) is Kleisli extension . The monad laws are what make program transformations valid: associativity justifies flattening nested do-blocks; the unit laws justify removing return x >>= f and m >>= return. Different monads give different effects (exceptions, state, nondeterminism, input/output, probability, continuations ), and monad morphisms translate between them. Later refinements — algebraic effects and handlers (Plotkin and Power; Plotkin and Pretnar) — return to the algebraic view: an effect is given by operations and equations, and its monad is the free-algebra monad of that theory (see formal methods and the λ-calculus).
Logic— Closure, possibility and modalities
On a preorder of propositions ordered by entailment, a monad is a closure operator with and . In the modal logic S4, possibility satisfies and , so it is a monad (and necessity a comonad). The modality of lax logic is a monad of this kind, and so are the Lawvere–Tierney topologies of topos theory, which in addition preserve conjunction and classify subtoposes such as categories of sheaves (see elementary topoi).
Category theory— A monoid, one dimension up
Endofunctors of form a strict monoidal category under composition, and a monad is a monoid object in it ( the multiplication, the unit). Street's formal theory of monads (1972) defines a monad in any 2-category as a 1-cell with 2-cells satisfying the usual laws; in these are the monads of this lesson, and (Bénabou) in the bicategory of spans of sets they are exactly the small categories. Kan extensions give the most general source of monads: every functor whose right Kan extension along itself exists has a codensity monad, which for a right adjoint is the induced monad (see Kan extensions).
Wonder Trails
History and global context
- Roger Godement (France) introduced the "standard construction" in Topologie algébrique et théorie des faisceaux (1958), to build flabby resolutions of sheaves.
- Peter J. Huber (Switzerland) showed in 1961, in work on homotopy theory in general categories, that every adjunction produces such a construction.
- In 1965 Heinrich Kleisli (Switzerland) and, independently, Samuel Eilenberg and John C. Moore showed that every monad arises from an adjunction, by the two constructions that now bear their names.
- Jon Beck proved the monadicity theorem in his 1967 Columbia thesis Triples, algebras and cohomology; F. William Lawvere's 1963 thesis on algebraic theories and Fred Linton's work connected monads to universal algebra; Ernest Manes characterised compact Hausdorff spaces as algebras in 1969. The name "monad" replaced "triple" in the late 1960s and was popularised by Mac Lane's 1971 textbook; it is usually credited to Jean Bénabou.
- Michèle Giry (1982), building on unpublished notes of Lawvere (1962), introduced the probability monad on measurable spaces.
- Eugenio Moggi (Italy, 1989 and 1991) used monads to structure the semantics of computational effects; Philip Wadler and Simon Peyton Jones brought them into functional programming, and Haskell adopted monadic input/output in the 1990s.
Further reading
- Saunders Mac Lane, Categories for the Working Mathematician (1971; 2nd edition 1998), Chapter VI — monads, algebras, the comparison theorems and Beck's theorem.
- Emily Riehl, Category Theory in Context (2016), Chapter 5 — a modern treatment with many examples, including Beck's theorem.
- Francis Borceux, Handbook of Categorical Algebra 2 (1994), Chapter 4 — monads in depth.
- Michael Barr and Charles Wells, Toposes, Triples and Theories (1985) — triples, algebraic theories and descent.
- Eugenio Moggi, "Notions of computation and monads", Information and Computation 93 (1991).
- Tom Leinster, Basic Category Theory (2014) — for the adjunctions this lesson builds on.