Types and Functions
Bartosz Milewski · 2014 · Category Theory for Programmers, Ch. 2
Prereqs: 🍞 Chapter 1 (Category). 5 min.
Types are sets. Functions are morphisms between them. The category Hask has Haskell types as objects and pure functions as arrows. Every type is extended with bottom (⊥), the value representing non-termination.
Types as sets
A type is a set of values. Bool is the two-element set {True, False}. Integer is an infinite set. A function between types is a mapping between sets. This is the category Set: objects are sets, morphisms are functions. Programming types live in the same structure.
Pure functions
A pure function always produces the same output for the same input and has no side effects. Pure functions are the only kind that qualify as morphisms in a category. Impure functions (I/O, mutation, randomness) break composability: if f's output depends on hidden state, g ∘ f is not determined by the types alone.
Bottom — the value that never arrives
Every Haskell type secretly includes one extra value: bottom (⊥), representing non-termination. A function that loops forever or throws an exception returns bottom. This makes Hask different from Set: in Set, every function terminates. In Hask, a function Bool → Bool can return True, False, or ⊥. Functions that may return bottom are partial; those that always return a proper value are total.
Void — the empty set
The type Void has no values at all. It corresponds to the empty set. You cannot call a function that takes Void as input, because you have nothing to pass it. But you can declare one: absurd :: Void → a. From falsity, anything follows. This is the Curry-Howard correspondence in action.
Unit — the singleton set
The type () (unit) has exactly one value. Functions from unit to a type A are in one-to-one correspondence with elements of A: each such function "picks" one value. The function unit :: a → () discards its argument and returns the single value. It is the unique morphism to the terminal object.
Bool — the two-element set
Bool has exactly two values. Functions to Bool are predicates: they classify inputs into true/false. Functions from Bool select a pair of values from the target type. Bool is the simplest interesting type: big enough to branch on, small enough to enumerate.
Notation reference
| Haskell | Scheme | Python | Meaning |
|---|---|---|---|
| Void | ; (no values) | NoReturn | Empty type (initial object) |
| () | 'unit | None | Unit type (terminal object) |
| Bool | #t / #f | True / False | Two-element set |
| f :: a -> b | (define (f x) ...) | def f(x): ... | Morphism from a to b |
| ⊥ (bottom) | (let loop () (loop)) | while True: pass | Non-termination |
| absurd :: Void -> a | (define (absurd v) ...) | def absurd(v): ... | From falsity, anything |
| unit :: a -> () | (define (to-unit x) 'unit) | def to_unit(x): None | Terminal morphism |
Neighbors
Milewski chapters
- 🍞 Chapter 1 — Category: the grand design
- 🐱 Chapter 3 — Categories great and small
Related paper pages
- 🍞 Spivak 2013 — categories of types in databases
- 🍞 Staton 2025 — programs as morphisms, same pattern
Foundations (Wikipedia)
Translation notes
All examples use total functions in Scheme and Python. The bottom example simulates non-termination with an infinite loop but does not actually run it. Milewski's treatment works in Hask, where every type includes bottom and the category is technically a CPO-enriched category rather than Set. The examples on this page stay in Set (no bottom, no laziness). For example: the four Bool→Bool functions enumerated above are exactly the four morphisms in Set. In Hask, there are additional "functions" that return bottom on some inputs, bringing the count higher. The enumeration is exact for Set. The Hask subtlety is noted but not demonstrated.
Framework connection: Types are the handshake between pipeline stages. The target type of one morphism must match the source type of the next. This is the composability constraint that the Natural Framework enforces at every stage boundary. (
The Handshake)