Category Theory for programmers
🧩 Chapter 2 · The things and the arrows, pinned down

Types are the things.
Pure functions are the arrows.

Chapter 1 said "a category is things and arrows." Now we say exactly which things and which arrows the programmer cares about — and meet the three tiniest types that do most of the heavy lifting.

Last chapter we built the stage: objects and arrows. We waved at the idea that "in programming, objects look like types and arrows look like functions." This chapter we stop waving.

We're going to nail down which functions actually count as arrows (spoiler: not all of them), and then we'll meet three tiny types whose entire job is to teach us something deep about uniqueness — that some arrows write themselves.

1Pure functions: the only honest arrows

A function is pure if it gives the same output for the same input and does nothing else. That's it. Same in, same out, no surprises on the side.

Math people have a fancier name — referential transparency — but it just means: you can replace the call with its result and your program still does the same thing. square(3) is interchangeable with 9, anywhere, forever.

Why do we care for category theory? Because the laws from Chapter 1 quietly assume this. "g ∘ f equals the function you get by running f then g" only makes sense if running them produces an answer determined by the input alone. The moment a function reads a clock or writes to disk, the equation breaks — the two sides might differ depending on when you run them, or what else has happened.

So the category programmers actually live in has:

  • Objectstypes (Int, String, List[User], …)
  • Arrowspure functions between those types

You'll hear it called Hask in Haskell circles, or just "types and total pure functions." For everything in this chapter, that's our world.

Try it · pure or impure? 0 / 6 graded
⚠️
The catch in real Scala/Haskell: any function that prints, reads the clock, throws, or pokes a mutable variable is breaking the deal. Scala doesn't track this in the type — discipline is on you. Haskell tracks it: anything impure has IO bolted onto its return type, so the compiler keeps it visible. Either way, the "honest arrows" of our category are the pure ones.

2Three special types: the 0, 1, and 2 of the type world

Numbers have tiny building blocks — 0, 1, 2 — that show up everywhere. Types have three that play the same role. They look almost too small to matter, but they're the seeds the whole "algebra of types" grows from.

  • Void — the type with no values. An empty room. No one's home. You can't construct one. In Scala this is Nothing; in Haskell it's Void (from Data.Void).
  • Unit — the type with exactly one value, written (). The room with one chair. Once you know the room, you know who's in it. In Scala this is Unit; in Haskell it's () (yes, also written like that).
  • Bool — the type with two values, true and false. Two chairs.

The number of values a type has is its cardinality. Here they are, side by side:

Cardinality · how many values fit?
Void
no inhabitants · |Void| = 0
Unit
()
exactly one · |Unit| = 1
Bool
true
false
exactly two · |Bool| = 2
💡
Counting values feels childish — until you notice it predicts how many distinct functions there are between two types. A function Bool → Bool has 4 possibilities (one output choice for each of 2 inputs: 2² = 4). A function Bool → Unit has just 1. This counting game IS the algebra of types, and we'll lean on it again next chapter.

3The unique arrows these tiny types force

Here's where it gets surprising. Unit and Void each force the existence of exactly one arrow for every other type — no choice, no freedom, the type signature alone determines the function.

Every type has exactly one arrow to Unit

Pick any type A. How many pure functions A → Unit are there? Well, the output has to be a value of Unit, and there's only one such value: (). So whatever the input, the function returns (). There's only one way to throw all the information away.

Try it: click any of the inputs below. They all land in the same place.

Interactive · the only arrow A → Unit Click an input — watch where it lands
bang : A → Unit   (only one such function exists)

Every type has exactly one arrow from Void

Now flip it. How many pure functions Void → A are there? Sounds absurd — how do you write a function whose input has no possible value?

Here's the trick: you don't have to. The function exists as a type signature, and the type checker is happy, because you'll never be called on to actually produce an A — there's no Void to feed in. It's an arrow you can write, never call, and that's fine. There's only one such arrow per target type (since there are no inputs to disagree about), and it has a name: absurd.

Think of it like this: if you ever did have a Void in your hands, you could conjure anything from it — including a contradiction. You never will, so the function is safe.

Interactive · the absurd arrow Void → A No inputs available — the arrow is never called
absurd : Void → A   (typechecks, never runs)
🏷️
Two pieces of vocab to file away: Unit is the place everything uniquely flows into — the math name is terminal object. Void is the place everything uniquely flows out of — the math name is initial object. You don't need the names yet. You just need to feel that "exactly one arrow" pattern. We'll come back to it.

4The same idea, in code

Two languages, same ideas. Scala calls Void Nothing (it's the bottom of Scala's type hierarchy, which is why it cleanly stands in for "no values"). Haskell needs to import Void from Data.Void. Beyond that, the shapes line up.

5Check yourself

3 questions · instant feedback 0 / 3