Category theory as a pattern language
Lesson 06 gave us the mathematical reading of our materials: types are sets and functions are maps between them, and from Lesson 05 we already know two pure functions compose into a third. This lesson names the structure that fact lives in. Category theory has a fearsome reputation, but the honest pitch is small and concrete: it is the study of things that compose, and we are already holding one — types and functions. Once you see that, the scary words of the next three lessons (functor, monoid, monad) stop being mysticism and become what they actually are: named patterns, each with a couple of laws.
compose/andThen), Lesson 04 (purity / referential transparency — the reason our functions compose lawfully at all).New capability: read category theory as a pattern language for lawful composition — enough to understand why functor, monoid, and monad are "just" named patterns with laws, and why "lawful" is what makes refactoring safe.
1 · It is the study of things that compose
You have probably been warned that category theory is forbidding. Set the warning aside and read the one-line honest version: category theory is the study of things that compose, and of doing so lawfully. That is the whole subject in a sentence. Wherever you have a collection of objects, arrows that go between them, and a way to chain arrows end-to-end that behaves sensibly, you have a category — and any theorem proved about categories in general is automatically true of yours.
The reason this matters to us right now is that we are already standing inside an example and have been for two lessons. In Lesson 05 we composed functions: given f: A => B and g: B => C, f andThen g is a function A => C. In Lesson 06 we read those functions as maps between types-as-sets. Chaining maps is exactly "arrows that compose." So before any definition, hold this anchor: every "object" below is a type, every "arrow" is a pure function, and "composition" is andThen. The abstract definition is just that anchor, with the type-specific parts erased so it can be reused.
2 · A category is four things and two laws
Here is the entire definition. It is short. Read each line abstractly, then immediately read the concrete gloss beside it.
Int, String, List[User]. The objects are opaque dots; we never look inside them, only at the arrows between them.A, B, a set of morphisms A → B. Concretely: pure functions A => B. An arrow has a source object and a target object — that is all that defines its "shape."A has an identity morphism idA: A → A. Concretely: Scala's identity[A], the function x => x that returns its input unchanged.f: A → B and g: B → C (target of one = source of next) compose into g ∘ f : A → C. Concretely: f andThen g, equivalently g compose f.Those four ingredients are not yet enough — a "way to chain arrows" is only useful if chaining behaves predictably. So a category demands exactly two laws:
Associativity says: when you chain three arrows, how you parenthesize the chaining does not change the result — only the order of the arrows matters. Identity says: composing with the do-nothing arrow does nothing; id is a neutral element for composition, the way 0 is for +. That is the entire definition of a category: four things, two laws. No prerequisites, no advanced machinery. Everything in the next three lessons is built on top of exactly this.
3 · The category we already have: types and pure functions
Claiming our types and functions form a category means more than waving at the analogy — it means the two laws actually hold. Let us check them, because the checking is the demystification. Take three concrete functions on Int:
val f: Int => Int = x => x + 1
val g: Int => Int = x => x * 2
val h: Int => Int = x => x - 3
// composition is andThen (read left-to-right) — same as g ∘ f written right-to-left
val left = (f andThen g) andThen h // group the first two
val right = f andThen (g andThen h) // group the last two
val id = identity[Int] // x => x
left at 5: f(5)=6, then g(6)=12, then h(12)=9. Now right at 5: f(5)=6, then (g andThen h)(6) = h(g(6)) = h(12)=9. Both give 9. The regrouping moved no parentheses that mattered — the arrows fired in the same order f, g, h either way. That is associativity: (f andThen g) andThen h = f andThen (g andThen h).Identity.
(f andThen id)(5) = id(f(5)) = id(6) = 6 = f(5); and (id andThen f)(5) = f(id(5)) = f(5) = 6. Sticking identity before or after a function changes nothing: f ∘ id = id ∘ f = f. The do-nothing arrow is the neutral element.Both laws hold, so Scala types as objects and pure functions as morphisms form a category — usually called Scal (the analogue of Set from Lesson 06, where objects are sets and arrows are total functions). One caveat carries straight over from Lesson 04: this only works because the functions are pure. If f printed to the console or mutated a counter, then f andThen id would print but f alone would print too — fine — yet "substitute f for f andThen id" stops being a safe rewrite once how many times an effect fires depends on grouping. Referential transparency is precisely the property that lets us treat a composed expression as equal to its result. The category structure is the payoff of purity, not an independent assumption.
4 · Why a programmer should care: laws make refactoring safe
Why spend a lesson on this vocabulary instead of more code? Two concrete reasons.
List is a functor, Option is a functor" means both get the same guarantees and the same combinators. The vocabulary is what makes that transfer precise instead of hand-wavy.map(identity) or a .andThen(id) from your pipeline and know the program is unchanged. Associativity is the licence to regroup a chain to read better, or to split it across functions, without retesting behaviour. Laws are the substitution rules of Lesson 04, now attached to structures instead of single expressions.Put bluntly: laws are what let you refactor by reasoning rather than by running. When a type's operations obey known laws, you can substitute equals for equals — that is the whole reasoning advantage the series promised in Lesson 00, generalized from individual functions to reusable patterns.
5 · The pattern ladder this unlocks
With "structure + laws" in hand, the next three lessons are each one rung of a ladder, and each has the identical shape: a pattern, its laws, and the types that satisfy it. Naming them now (precise definitions come in their own lessons) makes the climb feel inevitable rather than mysterious:
| Lesson | Pattern | Categorical reading | The win |
|---|---|---|---|
| 08 | Functor | A structure-preserving map between categories: F[_] with map, that carries arrows to arrows while preserving id and composition. | Apply a plain function inside a context (List, Option) without unwrapping. |
| 09 | Monoid | An algebra with one lawful binary combine and an identity element — a one-object category whose arrows are the elements. Laws: associativity + identity. | Combine many values into one in any grouping → foldable, parallelizable. |
| 10 | Monad | A functor with extra structure (flatMap) and its own three laws — built directly on the functor of Lesson 08. | Sequence context-carrying steps; the engine behind for-comprehensions. |
Notice the laws repeat: the monoid's two laws are the very category laws we just checked, applied to elements instead of functions (a monoid is literally a category with a single object). The functor laws are "preserves identity, preserves composition" — again the same two ideas, now about a map between categories. You are not learning three unrelated mysteries; you are learning one idea — lawful composition — wearing three costumes.
Common mistakes / failure modes
A => B qualifies. An effectful function can break the identity/associativity guarantees because when and how often the effect runs depends on grouping. The category is the category of pure functions (Lesson 04).combine(a, b) = a + b.trim (drop trailing/leading space on the right). Is it associative? Try a = "x ", b = " ", c = "y". Left grouping: combine(combine("x ", " "), "y") = combine("x ", "y") = "x y". Right grouping: combine("x ", combine(" ", "y")) = combine("x ", "y") = "x y" — looks fine, but now try a="x", b=" y ", c="z": left = combine(combine("x"," y "),"z") = combine("xy","z") = "xyz"; right = combine("x", combine(" y ","z")) = combine("x"," y z") = "xy z". Different results. A pipeline that folds left and one that folds right (or chunks the work across threads) now disagree — and nothing in the type tells you. With a lawful combine (plain +), grouping is irrelevant and you may regroup or parallelize freely. That guarantee is the law.Checkpoint exercise
A, and what operation is composition? (2) Take f = _ + 10, g = _ * 3, h = _ - 4 (all Int => Int) and verify associativity by evaluating (f andThen g) andThen h and f andThen (g andThen h) at x = 2; confirm they agree. (3) Confirm f andThen identity[Int] and identity[Int] andThen f both equal f at the same input. (4) In one sentence, state what the associativity law lets you safely do to a long composition chain in real code.Where this points next
We have the pattern language; now we climb the first rung. Lesson 08 introduces the functor — the first named pattern. A functor is a context F[_] (think List, Option) equipped with map, which lifts a plain function A => B so it acts inside the context, turning F[A] into F[B]. Categorically it is a structure-preserving map between categories, and its two laws will be exactly the two ideas you just checked: mapping identity changes nothing, and mapping g compose f equals mapping f then mapping g. You will recognize the shape immediately — it is this lesson's category laws, lifted one level up.
identity is the identity morphism, and andThen is composition — and the two category laws (associativity, f ∘ id = id ∘ f = f) provably hold, because the functions are pure (Lesson 04). The reason to care is practical: a pattern stated as structure + laws is proven once and transfers to every type that satisfies it, and "lawful" is exactly the licence to substitute equals for equals — to delete a redundant map(identity) or regroup a chain — without changing behaviour, where a lawless combine quietly breaks that. The next three lessons are one idea in three costumes: a functor (Lesson 08) is a structure-preserving map between categories, a monoid (Lesson 09) is a one-object category (combine + identity, same two laws), and a monad (Lesson 10) builds on the functor — each a named pattern with its laws and the types that satisfy it.Interview prompts
- Give the one-sentence honest pitch for category theory to a working programmer. (§1 — it is the study of things that compose, lawfully; you already know an example, since types-and-functions compose via
andThen.) - State the full definition of a category. (§2 — objects; morphisms/arrows between objects; an identity morphism per object; composition of arrows whose ends meet; plus two laws: associativity of composition and identity as a neutral element.)
- Spell out the types-and-functions category concretely, ingredient by ingredient. (§3 — objects = Scala types, morphisms = pure functions
A => B, identity =identity[A], composition =andThen/compose; the laws hold because functions are pure.) - Why does the category require the functions to be pure? (§3 — referential transparency lets a composed expression equal its result; with effects, how often/when an effect fires can depend on grouping, breaking the identity and associativity guarantees.)
- What does "lawful" buy you in day-to-day code? (§4 — safe refactoring: associativity lets you regroup/split a chain, identity lets you drop a redundant
map(identity), and you can substitute equals for equals without retesting behaviour.) - Show that a non-associative "combine" is dangerous. (§ common mistakes — a lawless combine gives different results for left vs right grouping, so a left-fold, right-fold, or parallel chunking disagree, with nothing in the type to warn you.)
- How do functor, monoid, and monad relate to the category laws? (§5 — a functor is a structure-preserving map between categories preserving identity and composition; a monoid is a one-object category with the same associativity+identity laws on elements; a monad is a functor plus extra lawful structure.)