all_lessons/functional programming/07 · Category theorylesson 8 / 15

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.

Book source
This maps to the book's bridge chapter — the point where Widman pauses the mechanics to give the reader the vocabulary of lawful composition before the functor/monoid/monad sequence. We follow that intent: introduce a category as the minimal structure that makes composition meaningful, show our own types-and-functions example satisfies it, and frame the rest of the series as a pattern ladder. Prose and examples here are original; the goal is to demystify, not to do mathematics for its own sake.
Linear position
Prerequisite: Lesson 06 (types as sets, functions as maps), Lesson 05 (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.
The plan
Five moves. (1) Reframe the subject: it is not abstract nonsense, it is the study of composition, and you already know an example. (2) Define a category as four things plus two laws, abstractly but with the concrete reading attached to each. (3) Show that Scala types and pure functions are a category, and actually check the two laws on real functions. (4) Say why a working programmer should care: lawful patterns proven once transfer everywhere, and lawfulness is what makes "substitute equals for equals" safe. (5) Preview the pattern ladder — functor, monoid, monad — so the next three lessons read as one shape repeated.

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.

objects
A collection of objects. Concretely: the Scala types — Int, String, List[User]. The objects are opaque dots; we never look inside them, only at the arrows between them.
morphisms (arrows)
For each pair of objects 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."
identity
Every object A has an identity morphism idA: A → A. Concretely: Scala's identity[A], the function x => x that returns its input unchanged.
composition
Any 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:   (h ∘ g) ∘ f = h ∘ (g ∘ f)
identity:   f ∘ id = id ∘ f = f

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
Worked check — both laws on a sample input, x = 5
Associativity. Evaluate 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.

Prove once, reuse everywhere
A pattern stated abstractly (a structure + its laws) is proven once. Every type that satisfies the pattern inherits the result for free — no re-derivation per type. "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.
Lawful = safe to substitute
"Lawful" is not decoration. The identity law is literally the licence to delete a redundant 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:

LessonPatternCategorical readingThe win
08FunctorA 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.
09MonoidAn 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.
10MonadA 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

Memorizing the abstraction
Trying to hold "object," "morphism," "composition" as free-floating math. Fix: never read an arrow without saying "function" and never read an object without saying "type." The concrete reading is not a crutch — for a programmer it is the meaning.
Thinking the laws are pedantry
"Associativity, identity — who cares?" They are the difference between a refactor that is safe and one that silently changes behaviour. See the worked failure below.
Forgetting purity is load-bearing
Believing any 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).
A lawless combine breaks refactoring
Suppose someone defines a "combine" for strings as 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

Try it
(1) For the category of Scala types and pure functions, name all four ingredients in our concrete vocabulary: what are the objects, what are the morphisms, what is the identity on a type 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.

Takeaway
Category theory is the study of things that compose, lawfully, and we already had an example: Scala types are objects, pure functions are morphisms, 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