h5i

Chapter 1

Propositions as Types

A theorem prover has to decide whether a proof is correct. A proof written in English cannot be checked by a computer, so we need a representation of proofs that a program can inspect. This chapter introduces the idea that makes everything else possible: a proof is a program, a proposition is its type, and checking a proof is type checking.

Proofs as programs

Here is a proof, in ordinary mathematical prose, of the proposition "AA implies BB":

Assume AA. Then ... (some argument) ... so BB holds.

The shape of this proof is a function. It takes something, a reason to believe AA, and produces something else, a reason to believe BB. If we take this literally and treat "a reason to believe AA" as a value of type AA, then a proof of A→BA \to B is a function from AA to BB, and the proposition A→BA \to B is the type of that function.

This observation is the Curry-Howard correspondence. It says that

  • a proposition is a type,
  • a proof of the proposition is a term of that type, and
  • the proposition AA is true exactly when some term tt of type AA exists, written t:At : A.

Three examples show how far this already goes. On the left is a proposition, in the middle a proof of it written as a function, and on the right the idea of the proof in words. Here λx. e\lambda x.\,e denotes the function that takes xx and returns ee.

Proposition Proof In words
P→PP \to P λp. p\lambda p.\,p Return the assumption.
A→B→AA \to B \to A λa. λb. a\lambda a.\,\lambda b.\,a Ignore the second assumption.
(A→B)→(B→C)→A→C(A \to B) \to (B \to C) \to A \to C λf. λg. λa. g (f a)\lambda f.\,\lambda g.\,\lambda a.\,g\,(f\,a) Compose the two implications.

Look at the third row again. To prove that implication is transitive, you assume A→BA \to B and B→CB \to C, and then, given AA, you apply the first assumption to get BB and the second to get CC. The proof term says exactly that: ff and gg are the assumptions, aa is the proof of AA, and g (f a)g\,(f\,a) applies them in order. Using an assumption is applying a function; this is the step that distinguishes real reasoning from merely restating hypotheses, and we will return to it in Chapter 3.

Quantifiers become dependent functions

The same idea extends to "for all". To prove that B(x)B(x) holds for every x:Ax : A, we give a method that, handed any particular xx, produces a proof of B(x)B(x). That is again a function, with one twist: its return type depends on the argument. For x=3x = 3 it returns a proof of B(3)B(3), for x=7x = 7 a proof of B(7)B(7). A function type whose result type may mention the argument is called a dependent function type, or a Π\Pi-type, and is written

Π(x:A). B(x).\Pi(x : A).\,B(x).

The ordinary function type A→BA \to B is the special case in which BB does not mention xx.

For example, "every proposition implies itself" quantifies over propositions. If Sort 0\Sort{0} is the type of propositions, the statement is

Π(P:Sort 0). P→P,\tPi{P}{\Sort{0}}{P \to P},

and its proof is the function that takes a proposition PP, then a proof pp of PP, and returns pp:

λ(P:Sort 0). λ(p:P). p.\tLam{P}{\Sort{0}}{\tLam{p}{P}{p}}.

Notice that the binders now carry types: λ(P:Sort 0)\lambda(P : \Sort{0}) says that PP ranges over propositions, and λ(p:P)\lambda(p : P) says that pp is a proof of the particular PP that was passed in. A type checker will need these annotations.

What the kernel does

This gives us the central idea behind a theorem prover.

Proving a theorem means constructing a term of the required type, and verifying a proof means type checking that term.

The kernel does not reason about truth. It implements typing rules, which say when a term has a type, and it mechanically checks that a given term has a given type under those rules. Whether that counts as proof checking depends on whether the typing rules faithfully implement a logic, which is the subject of Chapter 4. Whether the rules are sound, so that no term inhabits the type of a false proposition, is the subject of Chapter 13 and Chapter 14.

This division of labour shapes the design of every serious prover: tactics, automation, decision procedures, and even a large language model may search for proofs and may contain bugs, because the kernel checks the term they produce. Only the kernel must be correct, so the goal is to keep it small. The kernel in this book is under fifteen hundred lines of Rust, comments included.

The plan

The chapters of Part I build the kernel for a type theory that is just large enough to serve as a logic.

  1. Chapter 2 fixes the language: universes, variables, Π\Pi-types, λ\lambda-abstractions, and application.
  2. Chapter 3 gives the typing rules, one per kind of term plus one that lets computation happen inside types.
  3. Chapter 4 shows that these rules are the rules of a logic, and defines consistency.
  4. Chapter 5 turns the rules into an algorithm and matches it against the Rust implementation.
  5. Chapter 6 runs the algorithm by hand on correct proofs, incorrect proofs, and proofs that need computation.

Two operations are deliberately treated as black boxes until Part III: substitution and definitional equality. Both are easy to state and easy to get wrong, and the kernel's correctness depends on them.

What to remember

  • Propositions are types; proofs are terms; t:At : A means that tt proves AA.
  • Implication is the function type. Using a hypothesis is applying a function.
  • Universal quantification is the dependent function type Π(x:A). B(x)\Pi(x : A).\,B(x).
  • A theorem prover's kernel is a type checker. Everything else only produces terms for it to check.