A monad is a functor M equipped with two natural transformations: return (η : a → M(a)) and join (μ : M(M(a)) → M(a)). Equivalently, bind (>>=) replaces both. The monad laws (associativity, left/right identity) are the Kleisli category's composition laws. Monads are the compositional glue for effects: partiality, nondeterminism, logging, state.
Return and join: the categorical definition
A monad on a category C is an endofunctor M : C → C with two natural transformations: return (η : Id → M) embeds a pure value, and join (μ : M∘M → M) collapses a nested layer. The monad laws say join is associative and return is its left and right identity. This is a monoid in the category of endofunctors.
bind (>>=) takes a monadic value M(a) and a function a → M(b), and produces M(b). It is equivalent to join composed with fmap: bind m f = join (fmap f m). Conversely, join = bind id. Bind is how programmers actually use monads: chain computations that may produce effects.
Scheme
; bind : M(a) -> (a -> M(b)) -> M(b); bind m f = join (fmap f m);; For Maybe:; bind Nothing f = Nothing; bind (Just x) f = f x
(define (maybe-bind mx f)
(if (eq? mx nothing)
nothing
(f (from-just mx))))
; Safe division: returns Nothing on divide-by-zero
(define (safe-div a b)
(if (= b 0) nothing (just (/ a b))))
; Chain two divisions: 12 / 3 / 2
(display "12 / 3 / 2: ")
(display (maybe-bind (safe-div 123)
(lambda (x) (safe-div x 2)))) (newline)
; Chain with a zero: 12 / 0 / 2
(display "12 / 0 / 2: ")
(display (maybe-bind (safe-div 120)
(lambda (x) (safe-div x 2)))) (newline)
; Three-step chain: 100 / 5 / 4 / 0
(display "100 / 5 / 4 / 0: ")
(display (maybe-bind (safe-div 1005)
(lambda (a)
(maybe-bind (safe-div a 4)
(lambda (b) (safe-div b 0))))))
; Nothing propagates. No null checks needed.
Three laws govern every monad. In terms of the fish operator (>=>, Kleisli composition): associativity (f >=> g) >=> h = f >=> (g >=> h), left identity return >=> f = f, right identity f >=> return = f. These are exactly the category axioms for the Kleisli category from Ch. 4.
Scheme
; The fish operator (Kleisli composition):; (>=>) : (a -> M b) -> (b -> M c) -> (a -> M c); (f >=> g) x = bind (f x) g
(define (fish f g)
(lambda (x) (maybe-bind (f x) g)))
; Three test functions
(define (f x) (just (* x 2)))
(define (g x) (just (+ x 10)))
(define (h x) (just (* x 3)))
; Law 1: Associativity; (f >=> g) >=> h = f >=> (g >=> h)
(define lhs ((fish (fish f g) h) 5))
(define rhs ((fish f (fish g h)) 5))
(display "assoc LHS: ") (display lhs) (newline)
(display "assoc RHS: ") (display rhs) (newline)
; Law 2: Left identity โ return >=> f = f
(display "left id: ")
(display ((fish maybe-return f) 7)) (newline)
(display "just f: ")
(display (f 7)) (newline)
; Law 3: Right identity โ f >=> return = f
(display "right id: ")
(display ((fish f maybe-return) 7)) (newline)
(display "just f: ")
(display (f 7))
; All equal. The Kleisli category axioms hold.
List monad: nondeterminism
The list monad models nondeterministic computation. return x = [x]. bind xs f = concat (map f xs). Each element spawns a list of possibilities; bind flattens them. This is the monad that arises from the free monoid adjunction (Ch. 16).
Scheme
; List monad: bind = concatMap; return x = (x); bind xs f = flatten (map f xs)
(define (list-return x) (list x))
(define (list-bind xs f)
(apply append (map f xs)))
; Nondeterministic choice: each die roll
(define (die-roll sides)
(let loop ((i 1) (acc '()))
(if (> i sides) (reverse acc)
(loop (+ i 1) (cons i acc)))))
(display "d4: ") (display (die-roll 4)) (newline)
; All pairs from two dice
(define two-dice
(list-bind (die-roll 3)
(lambda (a)
(list-bind (die-roll 3)
(lambda (b)
(list-return (list a b)))))))
(display "3x3 pairs: ") (display two-dice) (newline)
; Filter: pairs that sum to 4
(define sum-to-4
(list-bind (die-roll 3)
(lambda (a)
(list-bind (die-roll 3)
(lambda (b)
(if (= (+ a b) 4)
(list-return (list a b))
'()))))))
(display "sum to 4: ") (display sum-to-4)
; Bind is flatMap. Empty list is failure. List monad = backtracking search.
Python
# List monad in Pythondef list_return(x): return [x]
def list_bind(xs, f):
return [y for x in xs for y in f(x)]
dice = [1, 2, 3]
pairs = list_bind(dice, lambda a:
list_bind(dice, lambda b:
list_return((a, b))))
print(f"3x3 pairs: {pairs}")
sum4 = list_bind(dice, lambda a:
list_bind(dice, lambda b:
[(a, b)] if a + b == 4else []))
print(f"sum to 4: {sum4}")
Writer monad revisited
The Writer monad pairs a value with a log (any monoid). return x = (x, mempty). bind (a, w) f = let (b, w') = f a in (b, w <> w'). This is the monad from Ch. 4's Kleisli category, now recognized as a proper monad with return, bind, and join. The tell function appends to the log without producing a value.
Scheme
; Writer monad: (value . log) where log is a monoid (lists here); return x = (x . ()); bind (a . w) f = let (b . w') = f(a) in (b . w ++ w')
(define (writer-return x) (cons x '()))
(define (writer-bind mx f)
(let* ((a (car mx))
(w (cdr mx))
(result (f a))
(b (car result))
(w2 (cdr result)))
(cons b (append w w2))))
(define (tell msg) (cons '() (list msg)))
; Logging pipeline: double then add-ten
(define (double n)
(cons (* n 2) (list "doubled")))
(define (add-ten n)
(cons (+ n 10) (list "added-ten")))
(define result
(writer-bind (double 5)
(lambda (n)
(writer-bind (add-ten n)
(lambda (m)
(writer-return m))))))
(display "result: ") (display (car result)) (newline)
(display "log: ") (display (cdr result))
; The log accumulates through bind. No global mutable state.
The laws are not just axioms: they are testable properties. Left identity says bind (return a) f = f a. Right identity says bind m return = m. Associativity says the order of bind nesting does not matter. If any law fails, composition breaks and the Kleisli category is not a category.
Scheme
; Verify all three monad laws for Maybe at concrete values.; Law 1: Left identity โ bind (return a) f = f a
(define (double x) (if (> x 100) nothing (just (* x 2))))
(define law1-lhs (maybe-bind (maybe-return 5) double))
(define law1-rhs (double 5))
(display "Left identity: ") (display law1-lhs)
(display " = ") (display law1-rhs) (newline)
; Law 2: Right identity โ bind m return = m
(define m (just 42))
(define law2-lhs (maybe-bind m maybe-return))
(display "Right identity: ") (display law2-lhs)
(display " = ") (display m) (newline)
; Also with Nothing
(define law2n-lhs (maybe-bind nothing maybe-return))
(display "Right id (Nothing): ") (display law2n-lhs)
(display " = ") (display nothing) (newline)
; Law 3: Associativity โ bind (bind m f) g = bind m (ฮปa. bind (f a) g)
(define (add10 x) (just (+ x 10)))
(define law3-lhs (maybe-bind (maybe-bind (just 3) double) add10))
(define law3-rhs (maybe-bind (just 3) (lambda (a) (maybe-bind (double a) add10))))
(display "Associativity: ") (display law3-lhs)
(display " = ") (display law3-rhs)
; All three laws hold. Maybe is a lawful monad.
Notation reference
Blog / Haskell
Scheme
Meaning
return :: a → m a
(maybe-return x)
Embed a pure value
(>>=) :: m a → (a → m b) → m b
(maybe-bind mx f)
Bind: chain a computation
join :: m (m a) → m a
(maybe-join mmx)
Collapse one layer of nesting
(>=>) :: (a → m b) → (b → m c) → (a → m c)
(fish f g)
Kleisli composition (fish)
Nothing / Just x
nothing / (just x)
Maybe values
concatMap f xs
(list-bind xs f)
List bind = flatMap
Writer (a, w)
(cons a w)
Writer = value + log pair
Neighbors
Milewski chapters
๐ Chapter 4 โ Kleisli Categories (where the fish operator was born)
๐ Fritz 2020 โ Markov categories axiomatize the probability monad. The Giry monad (and its Kleisli category Stoch) is the formal backbone of the handshake between category theory and probability.
๐ Staton 2025 โ monads structure the semantics of effectful programs
This page combines material from three of Milewski's blog posts: "Monads: Programmer's Definition" (return, bind, do notation), "Monads and Effects" (IO, state, exceptions as monadic effects), and "Monads Categorically" (the endofunctor definition with η and μ). The Scheme translations omit do notation (Scheme has no syntactic equivalent) and IO (Scheme side-effects freely). The blog post proves that any type supporting bind and return is automatically a functor via fmap f m = bind m (λa. return (f a)). The fish operator (>=>) is Kleisli composition from Ch. 4, now seen as the morphism composition of the Kleisli category. The three monad laws are exactly the category axioms (associativity, left and right identity) for that category. The categorical definition via join (μ) makes the connection to adjunctions explicit: if a monad M factors as R∘L for an adjunction L ⊢ R, then join = R∘ε∘L where ε is the counit.
Ready for the real thing? Read the blog post. Start with the fish operator, then see how bind and join are equivalent formulations.