What is a proof?
Here is a complete, machine-checked Lean proof that implies " implies ":
theorem k_combinator (A B : Prop) : A → B → A :=
fun a _ => a
The proof is not a list of sentences. It is a program: the function that takes a proof a of , ignores a proof of , and hands back a. Lean checks the proof by checking that this program has the type A → B → A — exactly as a compiler checks that a function returning an integer really returns an integer.
This is the propositions-as-types principle, also called the Curry–Howard correspondence: a proposition is a type, a proof is a term of that type, and proving is programming. It goes back to the constructive reading of logic by Brouwer, Heyting and Kolmogorov, was observed by Haskell Curry and made precise by William Howard, and grew, through the work of Per Martin-Löf, Jean-Yves Girard, Thierry Coquand and others, into the dependent type theories on which Lean, Rocq (formerly Coq) and Agda are built.
Type theory began elsewhere: Bertrand Russell introduced types in 1908 to block paradoxes such as "the set of all sets that are not members of themselves". So type theory is at once a logic, a programming language and a foundation of mathematics. This lesson develops all three, building on the λ-calculus and first-order logic.
The constructive reading of the connectives
What must one have in order to have a proof? The Brouwer–Heyting–Kolmogorov (BHK) interpretation answers connective by connective:
| A proof of … | is … | as a type |
|---|---|---|
| a pair: a proof of and a proof of | product | |
| a proof of or a proof of , labelled with which | sum | |
| a method turning any proof of into a proof of | function type | |
| trivial | unit type | |
| impossible: there is none | empty type | |
| a method turning a proof of into a proof of | ||
| a function sending each to a proof of | -type | |
| a witness together with a proof of | -type |
Read this way, a proof of must decide which disjunct holds, and a proof of must produce a witness. That is why excluded middle, , is not automatically available: a method for deciding every proposition is a lot to ask. The resulting logic is intuitionistic. It proves fewer theorems than classical logic, but each of its proofs carries computational content.
Definitions
Definition— typing judgement
A typing judgement asserts that the term has type in the context , a list of typed variables (hypotheses). A judgement is not a proposition that can be true or false inside the theory; it is the basic act of the theory, checked by rules.
Definition— simply typed λ-calculus with products and sums
Types: base types , and , , , , . The typing rules come in pairs, an introduction rule that builds a term of a type and an elimination rule that uses one:
together with and . Computation rules say that eliminating what was just introduced cancels: , , and applied to continues with the first branch.
Definition— Curry–Howard correspondence
Erasing the terms from these rules leaves the rules of natural deduction for intuitionistic propositional logic: -introduction discharges a hypothesis, -elimination is modus ponens, and so on. So: propositions correspond to types, proofs to terms, provability to inhabitation of a type, and the computation rules to the removal of detours — an introduction immediately followed by an elimination.
Definition— dependent types: Π and Σ
A type family assigns a type to each — for example , the lists of length exactly , or the proposition for fixed . An element of the dependent function type sends each to an element of — the output type depends on the input. An element of the dependent pair type is a pair with and . When is a family of propositions, is and is . When is constant, and .
Definition— inductive type
An inductive type is freely generated by its constructors. The natural numbers have constructors and ; lists over have and . Each comes with a recursor (eliminator). For :
with computation rules and . With constant it defines functions by recursion; with a family of propositions it is the principle of induction.
Definition— universes
Types are terms too, so they need types: universes . Lean also has the universe of propositions, in which any two proofs of the same proposition are equal (proof irrelevance). A single universe containing itself, , makes the theory inconsistent (Girard's paradox, 1972) — a type-theoretic echo of Russell's paradox.
Examples: proofs as programs
Example— Three classical combinators are three tautologies
- proves .
- proves .
- .
The typing derivation of is a natural-deduction proof: assume , , ; then and by modus ponens, so ; discharge the three assumptions. The composition is the transitivity of implication.
Example— Negation and double negation
With :
- (given , any refutation of yields ).
- .
- , contraposition.
- : excluded middle cannot be refuted.
But no closed term has type , or , or Peirce's law , for a base type — although all three are classical tautologies. Theorem 4 proves this for Peirce's law.
Example— Products and functions: currying
and . Logically: " implies " is equivalent to " implies that implies ". The two terms are mutually inverse up to βη-equality — a type isomorphism, not merely a logical equivalence.
Example— Dependent types
- proves — and you can run it to compute a witness.
- is the type of positive natural numbers: a number paired with a certificate.
- : the type records the length, so a type checker can rule out reading past the end of a list.
Example— Recursion and induction from one recursor
Addition on by recursion on the second argument: , . Then holds by computation, but does not — it needs induction: , and if then . Both the definition and the proof are applications of the same recursor, with different .
The main theorems
We work with the implicational fragment — types built from base types by — where everything can be proved completely (and is, in Lean); the extensions to , , , add more cases of the same kind.
Natural deduction has three rules: if then ; from infer ; from and infer .
Theorem— Theorem 1 (Curry–Howard)
For every context and type : is derivable in natural deduction if and only if there is a term with .
Proof
() By induction on the typing derivation, erase the terms: a variable becomes the hypothesis ; an abstraction becomes -introduction; an application becomes -elimination.
() By induction on the natural-deduction derivation, build terms: a hypothesis becomes the variable ; if is realised by (with a new variable ), then is realised by ; if and are realised by and , then is realised by .
∎
The correspondence goes further than provability. A detour in a proof — introduce by discharging , then immediately apply it to a proof of — is a β-redex , and removing it means substituting the proof for the hypothesis , which is β-reduction. Types are preserved by reduction (subject reduction: if and then , proved from a substitution lemma for typings). So Prawitz's normalisation theorem for natural deduction (1965) and the normalisation of the simply typed λ-calculus (Turing; Tait 1967 for strong normalisation; see the λ-calculus) are the same theorem.
Theorem— Corollary 2 (self-application is not typable)
For no context and types is .
Proof
A typing of under requires and for some , so and , giving . That is impossible: by induction on , no type equals a function type out of itself ( has more symbols than ).
∎
So the terms and of the untyped calculus have no types — as they must not, since under Curry–Howard a non-terminating term of every type would prove every proposition.
To show that a type is empty we need a semantics in which the rules are sound but the type is "false".
Definition— Kripke model
A Kripke model is a preordered set of states of knowledge together with, for each state , a set of atoms known at , such that knowledge persists: if and is known at , then is known at . Forcing is defined by recursion on types: if is known at , and
Theorem— Theorem 3 (soundness of natural deduction for Kripke models)
(a) Persistence: if and then . (b) If and for every , then .
Proof
(a) Induction on . For atoms this is the persistence condition. For : if and , then every also satisfies (transitivity), so implies ; hence .
(b) Induction on the derivation, for all states at once. Hypothesis rule: immediate. -introduction: suppose and forces all of . Let with . By (a), forces all of , so forces all of , and the induction hypothesis gives . Hence . -elimination: from and , apply the definition with (reflexivity) to get .
∎
Theorem— Theorem 4 (Peirce's law is uninhabited)
Let be distinct base types. There is no closed term with , although this type is a classical tautology.
Proof
Take two states , with known only at and known nowhere.
- : at the later state , holds and does not. Similarly .
- Hence : at and at the hypothesis is not forced, so the implication holds vacuously.
- But . So , taking in the definition.
By Theorem 3, Peirce's law is not derivable in natural deduction, and by Theorem 1 no closed term has its type. It is a classical tautology: if is true the conclusion holds, and if is false then is true, so forces to be true — a contradiction, so that case cannot occur.
∎
With forcing extended by iff or , and forced nowhere, the same two-state model refutes excluded middle: , and because becomes known at . Conversely, Glivenko's theorem (1929) says that for every classical propositional tautology , the double negation is intuitionistically provable — so classical logic is "intuitionistic logic seen through a double negation".
Theorem— Theorem 5 (induction is the recursor)
In a type theory with the inductive type and its recursor, the induction principle
is inhabited, by the recursor itself. Moreover the constructors are injective and have disjoint images, so is freely generated by them. The same holds for lists, with the principle " and imply for all ".
Proof
Induction is with motive . For disjointness, define by recursion with and ; if , transporting the proof of along this equality gives a proof of . For injectivity, define by recursion with ; if then . Lists are identical in structure.
∎
Note how much the recursor does: in ZFC, is constructed as a set (the least inductive set), and induction is a theorem about it; in type theory, is defined by its constructors and its eliminator, and induction is part of the definition.
Theorem— Theorem 6 (the type-theoretic axiom of choice)
For types and a relation , the type
is inhabited, without any axiom.
Proof
The term has this type: assigns to each a pair, and the choice function is its first component.
∎
This explains a famous difference. In type theory a proof of built from Σ already is a choice function. Lean, however, puts in , where proofs are irrelevant and cannot be inspected to extract a witness; there, the corresponding statement is Lean's Classical.axiomOfChoice, derived from the axiom Classical.choice. Excluded middle is then provable (Diaconescu's argument), so Lean's mathematics is classical — by explicit axioms on top of a constructive core.
Theorem— Theorem 7 (normalisation and consistency, stated)
Every term of the simply typed λ-calculus with products and sums is strongly normalising, and closed normal terms of a type are built by an introduction rule of that type. Hence there is no closed term of type : intuitionistic propositional logic is consistent. Type checking is decidable. (Proofs: Girard, Lafont and Taylor 1989; Sørensen and Urzyczyn 2006.)
For stronger theories the analogous statements are deep: strong normalisation of Girard's System F (1972) has no proof in second-order arithmetic, and Lean's theory, with its impredicative , proof irrelevance and quotients, is known to have an undecidable definitional equality in general, while being consistent relative to ZFC with inaccessible cardinals (Carneiro 2019).
Type theory in Lean — and about Lean
The Lean module has two halves: Lean's own logic used as a type theory, and a small type theory defined inside Lean, about which we prove theorems.
Proofs are programs. Each proof is a λ-term. Look at the axiom lists Lean reports: the first four use no axioms at all, while Peirce's law depends on Classical.choice, through Classical.em.
/-- **A proof is a program.** The proof of `A → (B → A)` is the function that takes a proof
`a` of `A`, ignores a proof of `B`, and returns `a`: the K combinator `λa. λb. a`. -/
theorem k_combinator (A B : Prop) : A → B → A :=
fun a _ => a/-- The S combinator `λf. λg. λa. f a (g a)` proves `(A → B → C) → (A → B) → A → C`. -/
theorem s_combinator (A B C : Prop) : (A → B → C) → (A → B) → A → C :=
fun f g a => f a (g a)/-- Conjunction is a pair type: a proof of `A ∧ B` is a pair of proofs, and swapping the pair
proves `B ∧ A`. -/
theorem and_comm_term (A B : Prop) : A ∧ B → B ∧ A :=
fun ⟨a, b⟩ => ⟨b, a⟩/-- Disjunction is a tagged union: a proof of `A ∨ B` is a proof of `A` or a proof of `B`,
labelled with which, and using it is a case analysis. -/
theorem or_elim_term (A B C : Prop) : A ∨ B → (A → C) → (B → C) → C :=
fun h f g =>
match h with
| Or.inl a => f a
| Or.inr b => g b/-- `¬¬(A ∨ ¬A)` has a **constructive** proof (no axioms): given `h : ¬(A ∨ ¬A)`, the function
`fun a => h (Or.inl a)` is a proof of `¬A`, and feeding `Or.inr` of it to `h` gives `False`. -/
theorem not_not_em (A : Prop) : ¬¬(A ∨ ¬A) :=
fun h => h (Or.inr fun a => h (Or.inl a))/-- **Peirce's law** `((A → B) → A) → A` is provable in Lean — but only by using the axiom of
excluded middle `Classical.em` (Lean derives it from `Classical.choice`). Its axiom list shows
this, unlike the K and S combinators above. -/
theorem peirce_classical (A B : Prop) : ((A → B) → A) → A :=
fun h => (Classical.em A).elim id fun na => h fun a => absurd a naΠ and Σ (Theorem 6). With subtypes {y // R x y} (a Σ-type whose second component is a proof), choice is a definition with no axioms; with ∃, it needs Classical.choice.
/-- A proof of `∀ n, ∃ m, n < m` is a dependent function: for each `n` it returns a witness
`n + 1` packaged with a proof that `n < n + 1`. `∀` is a Π-type and `∃` a (propositional)
Σ-type. -/
theorem exists_larger : ∀ n : Nat, ∃ m, n < m :=
fun n => ⟨n + 1, Nat.lt_succ_self n⟩/-- **The type-theoretic axiom of choice is a theorem.** If for every `x : A` we are *given*
some `y : B` with `R x y` (an element of the Σ-type `{y // R x y}`), then the first components
form a function `f` with `R x (f x)` for all `x`. No axiom is used: in type theory, a proof of
"for all x there is a y" built from Σ already contains the choice function. -/
def ttChoice {A B : Type} {R : A → B → Prop} (h : ∀ x : A, {y : B // R x y}) :
{f : A → B // ∀ x, R x (f x)} :=
⟨fun x => (h x).1, fun x => (h x).2⟩/-- With the **propositional** `∃`, which forgets its witness, the same statement needs an axiom:
this applies core Lean's `Classical.axiomOfChoice`, which is proved from the axiom
`Classical.choice`. -/
theorem prop_choice {A B : Type} {R : A → B → Prop} (h : ∀ x : A, ∃ y : B, R x y) :
∃ f : A → B, ∀ x, R x (f x) :=
Classical.axiomOfChoice hInductive types (Theorem 5). A home-made copy of the natural numbers: the induction principle is literally N.rec with the predicate as motive, is proved with it, and freeness comes from the recursor. Then lists.
/-- The natural numbers as an **inductive type**: freely generated by `zero` and `succ`. Lean
generates the recursor `N.rec` from this declaration. -/
inductive N where
| zero : N
| succ : N → N/-- **Induction is the recursor.** To prove `P n` for all `n : N`, prove `P zero` and
`P n → P (succ n)`; the proof term is the recursor `N.rec` applied to the two cases, with the
predicate `P` as its motive. -/
theorem N.induction (P : N → Prop) (h0 : P N.zero) (hs : ∀ n, P n → P (N.succ n)) :
∀ n, P n :=
fun n => N.rec (motive := P) h0 hs n/-- Addition by **recursion** on the second argument: `m + 0 = m`, `m + succ n = succ (m + n)`. -/
def N.add : N → N → N
| m, N.zero => m
| m, N.succ n => N.succ (N.add m n)/-- `0 + n = n` is **not** true by computation (addition recurses on the *second* argument), so
it needs induction: `0 + 0 = 0` by definition, and `0 + succ n = succ (0 + n) = succ n`. -/
theorem N.zero_add (n : N) : N.add N.zero n = n :=
N.induction (fun n => N.add N.zero n = n) rfl (fun _ ih => congrArg N.succ ih) n/-- **Constructors are disjoint**, derived from the recursor: the function `isZero` (defined
by recursion into `Prop`) sends `zero` to `True` and every `succ n` to `False`, so
`succ n = zero` would turn `True` into `False`. -/
theorem N.succ_ne_zero (n : N) : N.succ n ≠ N.zero := by
intro h
let isZero : N → Prop := fun m => N.rec (motive := fun _ => Prop) True (fun _ _ => False) m
have hz : isZero N.zero := trivial
rw [← h] at hz
exact hz/-- **Constructors are injective**, also from the recursor: the predecessor function (defined
by pattern matching, which compiles to the recursor) recovers `n` from `succ n`. -/
theorem N.succ_injective {m n : N} (h : N.succ m = N.succ n) : m = n :=
congrArg (fun k => match k with
| N.zero => N.zero
| N.succ k => k) h/-- Lists over `α` as an inductive type, freely generated by `nil` and `cons`. -/
inductive Lst (α : Type) where
| nil : Lst α
| cons : α → Lst α → Lst α/-- The induction principle for lists, again the recursor: prove `P nil`, and `P l → P (a :: l)`
for every `a`. -/
theorem Lst.induction {α : Type} (P : Lst α → Prop) (hnil : P Lst.nil)
(hcons : ∀ a l, P l → P (Lst.cons a l)) : ∀ l, P l :=
fun l => Lst.rec (motive := P) hnil hcons l/-- `length (l₁ ++ l₂) = length l₁ + length l₂`, by list induction on `l₁`. -/
theorem Lst.length_append {α : Type} (l₁ l₂ : Lst α) :
(Lst.append l₁ l₂).length = l₁.length + l₂.length := by
induction l₁ using Lst.induction with
| hnil => simp [Lst.append, Lst.length]
| hcons a l ih =>
simp only [Lst.append, Lst.length, ih]
omegaCurry–Howard as a theorem (Theorem 1). Simple types, terms with de Bruijn indices, the typing judgement and natural deduction, defined as inductive predicates. The typing derivation of is built rule by rule, and the two directions of Theorem 1 are proved by induction on derivations.
/-- Simple types, which double as formulas of implicational logic: base types `p₀, p₁, …`
(atomic propositions) and function types `A ⇒ B` (implications). -/
inductive Ty where
/-- The base type (atomic proposition) number `n`. -/
| base : Nat → Ty
/-- The function type `A ⇒ B` (the implication "A implies B"). -/
| arrow : Ty → Ty → Ty
deriving DecidableEq/-- Terms of the simply typed λ-calculus, Church style: a λ records the type of its variable.
Variables are de Bruijn indices: `var 0` is the variable bound by the nearest `lam`. -/
inductive Tm where
| var : Nat → Tm
| app : Tm → Tm → Tm
| lam : Ty → Tm → Tm/-- The **typing judgement** `Γ ⊢ t : A`, where the context `Γ` lists the types of the free
variables (index `0` first). The three rules: a variable has its declared type; if `s : A ⇒ B`
and `t : A` then `s t : B`; if `t : B` assuming `x : A`, then `λ(x : A). t : A ⇒ B`. -/
inductive HasType : List Ty → Tm → Ty → Prop where
| var {Γ : List Ty} {n : Nat} {A : Ty} : Γ[n]? = some A → HasType Γ (Tm.var n) A
| app {Γ : List Ty} {s t : Tm} {A B : Ty} :
HasType Γ s (A ⇒ B) → HasType Γ t A → HasType Γ (Tm.app s t) B
| lam {Γ : List Ty} {t : Tm} {A B : Ty} :
HasType (A :: Γ) t B → HasType Γ (Tm.lam A t) (A ⇒ B)/-- **Natural deduction** for implication, `Γ ⊢ A`: use a hypothesis; →-introduction (discharge
a hypothesis `A` to prove `A ⇒ B`); →-elimination (modus ponens). -/
inductive Derivable : List Ty → Ty → Prop where
| hyp {Γ : List Ty} {A : Ty} : A ∈ Γ → Derivable Γ A
| impI {Γ : List Ty} {A B : Ty} : Derivable (A :: Γ) B → Derivable Γ (A ⇒ B)
| impE {Γ : List Ty} {A B : Ty} : Derivable Γ (A ⇒ B) → Derivable Γ A → Derivable Γ B/-- The S combinator as a typed term: `⊢ λf. λg. λx. f x (g x) : (A ⇒ B ⇒ C) ⇒ (A ⇒ B) ⇒ A ⇒ C`.
With de Bruijn indices `x = 0`, `g = 1`, `f = 2`. The typing derivation is built rule by rule. -/
theorem s_typed (A B C : Ty) :
HasType [] (Tm.lam (A ⇒ B ⇒ C) (Tm.lam (A ⇒ B) (Tm.lam A
(Tm.app (Tm.app (Tm.var 2) (Tm.var 0)) (Tm.app (Tm.var 1) (Tm.var 0))))))
((A ⇒ B ⇒ C) ⇒ (A ⇒ B) ⇒ A ⇒ C) :=
HasType.lam (HasType.lam (HasType.lam
(HasType.app (HasType.app (HasType.var rfl) (HasType.var rfl))
(HasType.app (HasType.var rfl) (HasType.var rfl)))))/-- **Curry–Howard, from terms to proofs**: erasing the term from a typing derivation leaves a
natural-deduction derivation. Variables become hypotheses, application becomes modus ponens and
λ becomes →-introduction. -/
theorem HasType.derivable {Γ : List Ty} {t : Tm} {A : Ty} (h : HasType Γ t A) :
Derivable Γ A := by
induction h with
| var hn => exact Derivable.hyp (List.mem_of_getElem? hn)
| app _ _ ihs iht => exact Derivable.impE ihs iht
| lam _ ih => exact Derivable.impI ih/-- **Curry–Howard, from proofs to terms**: every natural-deduction derivation is the shadow of
a typed term. A hypothesis `A ∈ Γ` is some position `n` of `Γ`, giving the variable `var n`. -/
theorem Derivable.exists_term {Γ : List Ty} {A : Ty} (h : Derivable Γ A) :
∃ t, HasType Γ t A := by
induction h with
| @hyp Γ A hA =>
obtain ⟨n, hn⟩ := List.mem_iff_getElem?.1 hA
exact ⟨Tm.var n, HasType.var hn⟩
| @impI Γ A B _ ih =>
obtain ⟨t, ht⟩ := ih
exact ⟨Tm.lam A t, HasType.lam ht⟩
| impE _ _ ih₁ ih₂ =>
obtain ⟨s, hs⟩ := ih₁
obtain ⟨t, ht⟩ := ih₂
exact ⟨Tm.app s t, HasType.app hs ht⟩/-- **The Curry–Howard correspondence** (implicational fragment): a formula is provable from
hypotheses `Γ` exactly when the corresponding type is inhabited in context `Γ`. -/
theorem curry_howard (Γ : List Ty) (A : Ty) : Derivable Γ A ↔ ∃ t, HasType Γ t A :=
⟨Derivable.exists_term, fun ⟨_, ht⟩ => ht.derivable⟩/-- **`λx. x x` has no simple type**, whatever type is declared for `x`: typing `x x` would need
`x : A ⇒ B` and `x : A` at once, so `A = A ⇒ B`, which `ne_arrow_self` forbids. So the
self-application behind `Ω` and the fixed-point combinators is ruled out by types. -/
theorem self_application_untypable (Γ : List Ty) (A T : Ty) :
¬ HasType Γ (Tm.lam A (Tm.app (Tm.var 0) (Tm.var 0))) T := by
intro h
cases h with
| lam hbody =>
cases hbody with
| app h₁ h₂ =>
cases h₁ with
| var e₁ =>
cases h₂ with
| var e₂ =>
simp only [List.getElem?_cons_zero, Option.some.injEq] at e₁ e₂
subst e₂
exact ne_arrow_self _ _ e₁Kripke models and Peirce's law (Theorems 3 and 4).
/-- A **Kripke model** for intuitionistic implicational logic: a preordered set of "states of
knowledge" and, for each state, the atomic propositions known there — knowledge only grows
along the order (`mono`). -/
structure KripkeModel where
World : Type
le : World → World → Prop
refl : ∀ w, le w w
trans : ∀ u v w, le u v → le v w → le u w
val : World → Nat → Prop
mono : ∀ u v n, le u v → val u n → val v n/-- **Forcing**: `w ⊩ A ⇒ B` means that at every later state where `A` holds, `B` holds too. -/
def KripkeModel.Forces (M : KripkeModel) : M.World → Ty → Prop
| w, Ty.base n => M.val w n
| w, Ty.arrow A B => ∀ v, M.le w v → M.Forces v A → M.Forces v B/-- **Persistence**: what is forced stays forced at later states. By induction on the formula;
the implication case uses transitivity of the order. -/
theorem forces_mono (M : KripkeModel) : ∀ (A : Ty) {u v : M.World}, M.le u v →
M.Forces u A → M.Forces v A
| Ty.base n, u, v, huv, h => M.mono u v n huv h
| Ty.arrow _ _, u, v, huv, h => fun w hvw hA => h w (M.trans u v w huv hvw) hA/-- **Soundness of natural deduction for Kripke models**: if `Γ ⊢ A` and every formula of `Γ`
is forced at `w`, then `A` is forced at `w`. →-introduction uses persistence of the hypotheses;
→-elimination uses reflexivity. -/
theorem Derivable.sound {Γ : List Ty} {A : Ty} (h : Derivable Γ A) (M : KripkeModel) :
∀ w, (∀ B ∈ Γ, M.Forces w B) → M.Forces w A := by
induction h with
| hyp hA => exact fun w hΓ => hΓ _ hA
| @impI Γ A B _ ih =>
intro w hΓ v hwv hA
apply ih v
intro C hC
cases List.mem_cons.1 hC with
| inl hCA => exact hCA ▸ hA
| inr hCΓ => exact forces_mono M C hwv (hΓ C hCΓ)
| impE _ _ ih₁ ih₂ =>
intro w hΓ
exact ih₁ w hΓ w (M.refl w) (ih₂ w hΓ)/-- A **two-state Kripke model**: states `false ≤ true`; the atom `p₀` becomes known only at
the later state `true`, and `p₁` is never known. -/
def peirceModel : KripkeModel where
World := Bool
le u v := u = false ∨ v = true
refl w := by cases w <;> simp
trans u v w := by cases u <;> cases v <;> cases w <;> simp
val w n := w = true ∧ n = 0
mono u v n := by cases u <;> cases v <;> simp_all/-- **Peirce's law is not intuitionistically derivable**: `⊬ ((p ⇒ q) ⇒ p) ⇒ p`. At the first
state, `p ⇒ q` is not forced (later `p` becomes true but `q` never does), so `(p ⇒ q) ⇒ p`
holds vacuously there — yet `p` does not hold there. Soundness then rules out a derivation. -/
theorem peirce_not_derivable :
¬ Derivable [] (((Ty.base 0 ⇒ Ty.base 1) ⇒ Ty.base 0) ⇒ Ty.base 0) := by
intro h
have hf := h.sound peirceModel false (fun _ hB => absurd hB List.not_mem_nil)
-- `(p ⇒ q) ⇒ p` is forced at `false`.
have hpq : peirceModel.Forces false ((Ty.base 0 ⇒ Ty.base 1) ⇒ Ty.base 0) := by
intro v _ hv
cases v with
| true => exact ⟨rfl, rfl⟩
| false =>
-- `p ⇒ q` fails at `false`: at `true`, `p` holds but `q` does not.
have hq := hv true (Or.inl rfl) ⟨rfl, rfl⟩
exact absurd hq.2 (by decide)
have hp := hf false (Or.inl rfl) hpq
exact absurd hp.1 (by decide)/-- **No closed λ-term has the type `((p ⇒ q) ⇒ p) ⇒ p`**: by Curry–Howard such a term would be a
derivation of Peirce's law. A classical tautology need not be an inhabited type. -/
theorem peirce_uninhabited :
¬ ∃ t, HasType [] t (((Ty.base 0 ⇒ Ty.base 1) ⇒ Ty.base 0) ⇒ Ty.base 0) :=
fun ⟨_, ht⟩ => peirce_not_derivable ht.derivableThere is a pleasing loop here. Lean proves peirce_classical, Peirce's law for its own propositions — using an axiom — and also proves peirce_uninhabited, that Peirce's law has no term in the simply typed λ-calculus. No contradiction: the first is about Lean's logic with Classical.choice, the second about a different, weaker formal system that Lean studies as a mathematical object.
What is and is not formalised. Theorems 1, 3, 4 (implicational fragment), 5 and 6 and Corollary 2 are proved independently, in core Lean without Mathlib. Not formalised: the extension to products, sums and the empty type, subject reduction, normalisation (Theorem 7), Glivenko's theorem, and anything about the metatheory of Lean itself.
Type inference and proof search
Given an untyped term, can we find its type? The first experiment infers the most general (principal) simple type by collecting equations between type variables and solving them by unification — the algorithm behind type inference in ML and Haskell. Read through Curry–Howard, the principal type is the most general formula the term proves, so each one is checked to be a tautology.
"""Inferring simple types: which λ-terms are proofs, and of what?
Given an untyped λ-term, we look for a simple type for it by collecting
equations between type variables and solving them by unification, as in the
Hindley-Milner algorithm (without let). If a type exists, the result is the
most general (principal) one: every other type of the term is an instance.
Read through Curry-Howard, the principal type of a closed term is the most
general formula it proves, so each one is checked to be a tautology. Terms
built on self-application, such as ω = λx. x x and the Y combinator, fail
the occurs check: no type A can equal A → B. The results are exact
computations for the terms listed.
"""
import itertools
import re
def parse(text):
"""Parse a named term: ``\\x. M`` or ``λx. M``, application, brackets.
Several binders may share one backslash: ``\\x y. M``."""
tokens = re.findall(r"[\\λ.()]|[A-Za-z0-9_']+", text)
pos = 0
def peek():
return tokens[pos] if pos < len(tokens) else None
def take():
nonlocal pos
if pos >= len(tokens):
raise ValueError(f"unexpected end of {text!r}")
pos += 1
return tokens[pos - 1]
def term():
if peek() in ("\\", "λ"):
take()
names = []
while peek() != ".":
names.append(take())
take()
body = term()
for name in reversed(names):
body = ("lam", name, body)
return body
result = None
while peek() not in (None, ")"):
arg = term() if peek() in ("\\", "λ") else atom()
result = arg if result is None else ("app", result, arg)
if result is None:
raise ValueError(f"empty term in {text!r}")
return result
def atom():
tok = take()
if tok == "(":
inner = term()
if take() != ")":
raise ValueError(f"missing ')' in {text!r}")
return inner
return ("var", tok)
result = term()
if pos != len(tokens):
raise ValueError(f"trailing input in {text!r}")
return result
class Untypable(Exception):
"""Raised when the equations have no solution."""
def resolve(t, sub):
"""Apply the substitution ``sub`` (type variable -> type) fully."""
while t[0] == "tv" and t[1] in sub:
t = sub[t[1]]
if t[0] == "fn":
return ("fn", resolve(t[1], sub), resolve(t[2], sub))
return t
def occurs(v, t, sub):
"""Does the type variable ``v`` occur in ``t``?"""
t = resolve(t, sub)
if t[0] == "tv":
return t[1] == v
return occurs(v, t[1], sub) or occurs(v, t[2], sub)
def unify(a, b, sub):
"""Extend ``sub`` so that a and b become equal, or raise Untypable."""
a, b = resolve(a, sub), resolve(b, sub)
if a == b:
return
if a[0] == "tv":
if occurs(a[1], b, sub):
raise Untypable(f"occurs check: t{a[1]} = {show(b)}")
sub[a[1]] = b
elif b[0] == "tv":
unify(b, a, sub)
else:
unify(a[1], b[1], sub)
unify(a[2], b[2], sub)
def infer(term):
"""The principal type of a closed term, with variables renamed a, b, …
in order of appearance; raises Untypable if there is none."""
sub, counter = {}, itertools.count()
def go(t, env):
if t[0] == "var":
if t[1] not in env:
raise ValueError(f"free variable {t[1]}")
return env[t[1]]
if t[0] == "lam":
a = ("tv", next(counter))
return ("fn", a, go(t[2], {**env, t[1]: a}))
f, x = go(t[1], env), go(t[2], env)
r = ("tv", next(counter))
unify(f, ("fn", x, r), sub)
return r
return rename(resolve(go(term, {}), sub))
def rename(t):
"""Rename type variables to 'a', 'b', … in order of first appearance."""
names = {}
def go(u):
if u[0] == "tv":
names.setdefault(u[1], "abcdefghijklmnopqrstuvwxyz"[len(names)])
return ("tv", names[u[1]])
return ("fn", go(u[1]), go(u[2]))
return go(t)
def show(t):
"""Print a type with right-associative arrows."""
if t[0] == "tv":
return t[1] if isinstance(t[1], str) else f"t{t[1]}"
left = show(t[1])
if t[1][0] == "fn":
left = f"({left})"
return f"{left} → {show(t[2])}"
def tautology(t):
"""Is the type, read as a formula of implicational logic, valid?"""
names = sorted({u[1] for u in walk(t) if u[0] == "tv"})
def value(u, v):
if u[0] == "tv":
return v[u[1]]
return (not value(u[1], v)) or value(u[2], v)
return all(value(t, dict(zip(names, row)))
for row in itertools.product([True, False], repeat=len(names)))
def walk(t):
"""All subtypes of a type."""
yield t
if t[0] == "fn":
yield from walk(t[1])
yield from walk(t[2])
EXAMPLES = [
("I", r"\x. x"),
("K", r"\x y. x"),
("S", r"\f g x. f x (g x)"),
("B (compose)", r"\f g x. f (g x)"),
("C (flip)", r"\f x y. f y x"),
("W", r"\f x. f x x"),
("K I", r"(\x y. x) (\z. z)"),
("Church 2", r"\f x. f (f x)"),
("plus", r"\m n f x. m f (n f x)"),
("λx. λk. k x", r"\x k. k x"),
("ω = λx. x x", r"\x. x x"),
("Y", r"\f. (\x. f (x x)) (\x. f (x x))"),
]
def main():
print("Principal types (read as formulas: each should be a tautology)")
for name, src in EXAMPLES:
try:
ty = infer(parse(src))
except Untypable as err:
print(f" {name:<13} untypable ({err})")
continue
mark = "tautology" if tautology(ty) else "NOT a tautology"
print(f" {name:<13} {show(ty):<38} {mark}")
print()
print("Epistemic status: exact computations for these terms. Unification "
"finds\nthe principal type whenever one exists (Hindley 1969, "
"Milner 1978); that\nevery simply typed closed term proves a "
"tautology is Curry-Howard plus\nsoundness, proved in the lesson. "
"A tautology need not have a term:\nPeirce's law is the standard "
"counterexample.")
if __name__ == "__main__":
main()and fail the occurs check — the equation of Corollary 2. Every principal type found is a tautology, as Curry–Howard predicts.
The converse fails, and the second experiment measures how badly. Truth tables decide classical validity; intuitionistic provability, i.e. inhabitation, is decided by Dyckhoff's contraction-free sequent calculus G4ip (1992), whose proof search always terminates.
"""Which tautologies are inhabited types? Truth tables versus proof search.
Under the Curry-Howard correspondence a propositional formula is a type, and
it has a closed λ-term exactly when it is provable in intuitionistic logic.
Truth tables decide *classical* validity. Intuitionistic provability is
decided by Dyckhoff's contraction-free sequent calculus G4ip (1992), whose
proof search always terminates. We compare the two on famous formulas
(Peirce's law, excluded middle, De Morgan), check Glivenko's theorem (a
classical tautology A always has an intuitionistic proof of ¬¬A), and survey
every implicational formula with at most four atoms over p, q. These are
exact decisions for the formulas listed, relying on the correctness of the
two decision procedures.
"""
import itertools
import re
BOT = ("bot",)
def parse(text):
"""Parse a formula: atoms are lowercase letters, ``~`` is negation
(A -> bot), then ``&``, ``|`` and right-associative ``->``."""
tokens = re.findall(r"->|[a-z]|[~&|()]|_\|_", text)
pos = 0
def peek():
return tokens[pos] if pos < len(tokens) else None
def take():
nonlocal pos
if pos >= len(tokens):
raise ValueError(f"unexpected end of {text!r}")
pos += 1
return tokens[pos - 1]
def imp():
left = disj()
if peek() == "->":
take()
return ("imp", left, imp())
return left
def disj():
left = conj()
while peek() == "|":
take()
left = ("or", left, conj())
return left
def conj():
left = unary()
while peek() == "&":
take()
left = ("and", left, unary())
return left
def unary():
tok = take()
if tok == "~":
return ("imp", unary(), BOT)
if tok == "(":
inner = imp()
if take() != ")":
raise ValueError(f"missing ')' in {text!r}")
return inner
if tok == "_|_":
return BOT
if re.fullmatch(r"[a-z]", tok):
return ("atom", tok)
raise ValueError(f"unexpected {tok!r} in {text!r}")
result = imp()
if pos != len(tokens):
raise ValueError(f"trailing input in {text!r}")
return result
def atoms(f):
"""The set of atom names in a formula."""
if f[0] == "atom":
return {f[1]}
if f == BOT:
return set()
return atoms(f[1]) | atoms(f[2])
def evaluate(f, v):
"""Truth value of ``f`` under the assignment ``v`` (dict atom -> bool)."""
kind = f[0]
if kind == "atom":
return v[f[1]]
if kind == "bot":
return False
a, b = evaluate(f[1], v), evaluate(f[2], v)
return {"imp": (not a) or b, "and": a and b, "or": a or b}[kind]
def tautology(f):
"""Classical validity, by checking every row of the truth table."""
names = sorted(atoms(f))
return all(evaluate(f, dict(zip(names, row)))
for row in itertools.product([True, False], repeat=len(names)))
def provable(goal, context=frozenset(), _memo=None):
"""Intuitionistic provability of ``context ⊢ goal`` by G4ip proof search.
Invertible rules are applied eagerly; the two non-invertible rules (choose
a disjunct on the right, or use a hypothesis (C -> D) -> B) are tried in
every possible way. Dyckhoff (1992) proved that this search terminates
and is complete for intuitionistic propositional logic.
"""
memo = {} if _memo is None else _memo
key = (context, goal)
if key in memo:
return memo[key]
memo[key] = False # guards against re-entry; G4ip never loops anyway
result = _search(context, goal, memo)
memo[key] = result
return result
def _search(ctx, goal, memo):
if BOT in ctx or goal in ctx:
return True
for f in ctx:
rest = ctx - {f}
if f[0] == "and":
return provable(goal, rest | {f[1], f[2]}, memo)
if f[0] == "or":
return (provable(goal, rest | {f[1]}, memo)
and provable(goal, rest | {f[2]}, memo))
if f[0] == "imp":
a, b = f[1], f[2]
if a == BOT:
return provable(goal, rest, memo)
if a[0] == "atom" and a in ctx:
return provable(goal, rest | {b}, memo)
if a[0] == "and":
return provable(goal, rest | {("imp", a[1], ("imp", a[2], b))}, memo)
if a[0] == "or":
return provable(goal, rest | {("imp", a[1], b), ("imp", a[2], b)},
memo)
if goal[0] == "imp":
return provable(goal[2], ctx | {goal[1]}, memo)
if goal[0] == "and":
return provable(goal[1], ctx, memo) and provable(goal[2], ctx, memo)
if goal[0] == "or" and (provable(goal[1], ctx, memo)
or provable(goal[2], ctx, memo)):
return True
for f in ctx:
if f[0] == "imp" and f[1][0] == "imp":
c, d, b = f[1][1], f[1][2], f[2]
rest = ctx - {f}
if (provable(("imp", c, d), rest | {("imp", d, b)}, memo)
and provable(goal, rest | {b}, memo)):
return True
return False
def implicational_formulas(max_atoms, names=("p", "q")):
"""Every implicational formula with 1..max_atoms atom occurrences."""
by_size = {1: [("atom", n) for n in names]}
for k in range(2, max_atoms + 1):
by_size[k] = [("imp", a, b)
for left in range(1, k)
for a in by_size[left] for b in by_size[k - left]]
return [f for k in range(1, max_atoms + 1) for f in by_size[k]]
def show(f):
"""Print a formula with minimal brackets."""
if f[0] == "atom":
return f[1]
if f == BOT:
return "⊥"
if f[0] == "imp" and f[2] == BOT:
inner = show(f[1])
return "¬" + (inner if f[1][0] == "atom" or f[1][2] == BOT
else f"({inner})")
op = {"imp": " → ", "and": " ∧ ", "or": " ∨ "}[f[0]]
left = show(f[1])
if f[1][0] not in ("atom", "bot") and not (f[1][0] == "imp" and f[1][2] == BOT):
left = f"({left})"
right = show(f[2])
if f[0] != "imp" and f[2][0] not in ("atom", "bot") and not (
f[2][0] == "imp" and f[2][2] == BOT):
right = f"({right})"
return left + op + right
FAMOUS = [
("K combinator", "p -> q -> p"),
("S combinator", "(p -> q -> r) -> (p -> q) -> p -> r"),
("Peirce's law", "((p -> q) -> p) -> p"),
("double negation elim.", "~~p -> p"),
("double negation intro.", "p -> ~~p"),
("triple negation", "~~~p -> ~p"),
("excluded middle", "p | ~p"),
("¬¬ excluded middle", "~~(p | ~p)"),
("contraposition", "(p -> q) -> ~q -> ~p"),
("converse contraposition", "(~q -> ~p) -> p -> q"),
("De Morgan (∨)", "~(p | q) -> ~p & ~q"),
("De Morgan (∧)", "~(p & q) -> ~p | ~q"),
("Dummett's axiom", "(p -> q) | (q -> p)"),
("affirming the consequent", "(p -> q) -> q -> p"),
]
def main():
print(f"{'formula':<31}{'classical':>10}{'inhabited':>11} name")
tautologies = []
for name, src in FAMOUS:
f = parse(src)
taut, prov = tautology(f), provable(f)
if taut:
tautologies.append(f)
print(f"{show(f):<31}{'yes' if taut else 'no':>10}"
f"{'yes' if prov else 'no':>11} {name}")
glivenko = all(provable(("imp", ("imp", f, BOT), BOT)) for f in tautologies)
print()
print(f"Glivenko: ¬¬A is inhabited for all {len(tautologies)} tautologies A"
f" above: {glivenko}")
forms = implicational_formulas(4)
taut = [f for f in forms if tautology(f)]
prov = [f for f in forms if provable(f)]
gap = [show(f) for f in taut if not provable(f)]
print(f"All {len(forms)} formulas built from p, q, → with <= 4 atoms:")
print(f" {len(taut)} classical tautologies, {len(prov)} inhabited types;")
print(f" tautologies without a λ-term: {', '.join(gap)}")
print()
print("Epistemic status: exact decisions for the formulas shown, assuming "
"the\ntruth-table check and the G4ip search are implemented "
"correctly (G4ip\nis proved terminating and complete by Dyckhoff, "
"1992). That Peirce's law\nhas no λ-term is proved in the lesson "
"and in Lean by a Kripke model.")
if __name__ == "__main__":
main()What to notice: Peirce's law, double-negation elimination, excluded middle, one of De Morgan's laws and the converse of contraposition are classical tautologies without λ-terms; their double negations all have terms (Glivenko). Among all implicational formulas in with at most four atoms, are tautologies and are inhabited — the gap consists exactly of Peirce's law and its mirror image. These are exact decisions for the listed formulas, resting on the correctness of the two decision procedures; Theorem 4 is the proof for Peirce's law.
Explore: a tautology with no proof term
Check in the truth table that Peirce's law is a classical tautology. Then compare it with type-theoretically: both are tautologies, but only one is an inhabited type.
Peirce's law
True in every row — yet no λ-term has this type. Truth tables check classical validity, not constructive proof.
Read as: ((p → q) → p) → p
Use the symbols ¬ ∧ ∨ → ↔, the words not, and, or, implies, iff, or ASCII ~ & | -> <->. Variables are single lowercase letters. ¬ binds tightest, then ∧, ∨, →, ↔; p → q → r means p → (q → r).
Tautology — true in all 4 rows.
| p | q | p → q | (p → q) → p | ((p → q) → p) → p |
|---|---|---|---|---|
| T | T | T | T | T |
| T | F | F | T | T |
| F | T | T | F | T |
| F | F | T | F | T |
4 rows: one for every way of making the 2 variables true or false.
The Kripke model of Theorem 4 has just two states. Knowledge grows from left to right, and each implication must hold at every later state.
Try it: decide at which states , , and are forced (with forced nowhere). You should find that is forced at although is not — the model refutes as well.
Exercises
1. Type theory and propositions as types
Warm-upHumanUnder the propositions-as-types correspondence, what is a proof of ?
2. Type theory and propositions as types
Warm-upHumanWhat is the most general simple type of , and hence which tautology does it prove?
3. Type theory and propositions as types
CoreHumanWhich of these types are inhabited by a closed term (equivalently, are theorems of intuitionistic propositional logic)? Here are base types and . Choose all that apply.
4. Type theory and propositions as types
CoreHumanIn the two-state Kripke model with known only at , which formulas are forced at ? (Rules: iff every forcing forces ; is forced nowhere, ; iff or .) Choose all that apply.
5. Type theory and propositions as types
CoreHumanLet be a base type. How many closed β-normal terms of type are there (counting α-equivalent terms once)?
6. Type theory and propositions as types
CoreHumanIn the proof of soundness for Kripke models (Theorem 3), the -introduction case goes: assume forces all of , let force ; then forces all of , so by induction. Which fact justifies ' forces all of '?
7. Type theory and propositions as types
CoreHumanWith addition on defined by and , why is true by computation while needs induction?
8. Type theory and propositions as types
StretchFormalThe lesson's Lean file proves both peirce_classical (A B : Prop) : ((A → B) → A) → A and peirce_uninhabited : ¬ ∃ t, HasType [] t (((Ty.base 0 ⇒ Ty.base 1) ⇒ Ty.base 0) ⇒ Ty.base 0). Why is this not a contradiction?
9. Type theory and propositions as types
StretchComputationalWrite forces(w, f, le, val) for implicational formulas in a finite Kripke model. A formula is either an atom (a string such as 'p') or a tuple ('imp', A, B). le is a set of pairs (u, v) meaning (reflexive and transitive), and val[w] is the set of atoms known at w. Use: atoms are forced when known; ('imp', A, B) is forced at w iff every v with (w, v) in le that forces A also forces B.
Common misconceptions
Misconception
"Type theory and set theory are interchangeable." They have different primitive notions. In ZFC everything is a set and membership is a global relation between any two objects; in type theory every term has a type as part of its very formation, and the basic act is the judgement , not a proposition. The two are related by interpretations — each can model the other with suitable extra assumptions — not by being the same theory.
Misconception
"Types are just sets." A set may belong to many unrelated sets ( and ), while a term has one type, fixed when it is written. In Lean, (2 : ℕ) ∈ (3 : ℕ) is not false; it is meaningless, and is rejected before anyone asks whether it is true. In ZFC with von Neumann numerals, is true — an accident of the encoding that type theory deliberately makes inexpressible.
Misconception
"Every program is a meaningful proof." Only terminating, total programs are. A non-terminating term of every type — such as a fixed-point combinator applied to the identity — would prove every proposition, including . That is why consistent type theories exclude unrestricted recursion: Lean accepts recursive definitions only when it can see that they terminate (structural or well-founded recursion), and functions marked partial cannot be unfolded in proofs.
Misconception
"Curry–Howard means proofs are classical, or that truth tables decide provability." The correspondence is with intuitionistic logic. Peirce's law and excluded middle are classical tautologies (every truth-table row is true) but uninhabited types. Classical reasoning can be added — by axioms, as in Lean, or via continuations (Griffin, 1990) — but it is not free.
Misconception
"An ∃-proof in Lean gives you a witness you can compute with." In Lean, ∃ x, P x lives in Prop, where proofs are irrelevant, so a proof cannot be pattern-matched to produce data. To compute, use a Σ-type such as {x // P x} (Theorem 6); to merely use existence inside proofs, Classical.choose is available at the price of an axiom.
Viewpoints
Logic— Intuitionism and the double-negation translation
For Brouwer, mathematics was a mental construction, and a proof of that produces no was not a proof at all. Heyting (1930) formalised the resulting logic, and Kripke (1965) gave it the possible-worlds semantics used in Theorem 4, for which it is complete. The two logics are tightly linked: Glivenko's theorem for propositional logic, and the Gödel–Gentzen negative translation for first-order logic and arithmetic, embed classical logic into intuitionistic logic by inserting double negations. So intuitionistic logic does not reject classical theorems; it makes finer distinctions, separating statements with computational content from those without. See proof theory.
Topology— Open sets as truth values, types as spaces
Tarski (1938) found a topological semantics of intuitionistic logic: interpret propositions as open subsets of a space, with , as intersection and union, as the interior of , and as the interior of the complement. On , take : then and , which is not all of — excluded middle fails at the boundary point . Every intuitionistic propositional theorem denotes the whole space, and the propositional formulas true in all such interpretations over are exactly the intuitionistic theorems. In homotopy type theory the picture deepens: types are spaces, terms are points, proofs of are paths, and Voevodsky's univalence axiom identifies equivalent types. See homotopy type theory.
Algebra— Heyting algebras
Algebraically, intuitionistic propositional logic is the theory of Heyting algebras: bounded lattices with an operation characterised by . Formulas modulo provable equivalence form the free Heyting algebra on the atoms (the Lindenbaum algebra), and a formula is provable exactly when it equals in every Heyting algebra. A three-element chain already refutes Peirce's law: with , , one computes , , and . Boolean algebras are exactly the Heyting algebras satisfying .
Computation— Proof assistants and certified programs
Dependent types let a program carry its specification: a sorting function of type cannot be written incorrectly, because the type checker verifies the proof. Proof assistants based on type theory have checked the Four Colour Theorem and the Feit–Thompson theorem (Gonthier and collaborators, in Coq, 2005 and 2012), the Kepler conjecture (Hales and collaborators, using HOL Light and Isabelle, 2014–17), and large parts of modern mathematics in Lean's Mathlib; verified compilers such as CompCert are written and proved correct in Coq. Type inference, as in the experiment above, is the everyday face of the same theory in ML, Haskell and Rust. See formal methods.
Category theory— Cartesian closed and locally cartesian closed categories
The simply typed λ-calculus with products is the internal language of cartesian closed categories (Lambek): types are objects, terms are arrows, and is the adjunction . Dependent types correspond to locally cartesian closed categories (Seely, 1984), where a type family over is an object of the slice category over and and are the left and right adjoints of pulling back. Toposes add a subobject classifier — a type of propositions. See categorical logic.
Types and sets: two foundations
| ZFC | Lean's type theory | |
|---|---|---|
| primitive notion | membership , one global relation | the typing judgement |
| what objects are | sets, all of one kind | terms, each with its type |
| logic | classical first-order logic, underneath the axioms | built in: propositions are types |
| natural numbers | constructed: , | an inductive type, with induction as its eliminator |
| functions | sets of ordered pairs | primitive (-terms) |
| choice | the axiom of choice | Classical.choice, an explicit axiom |
| paradox control | separation instead of comprehension | universes |
Neither foundation is "the right one"; they are mutually interpretable given extra assumptions. Mathlib builds a model of ZFC inside Lean (ZFSet, from a type of well-founded trees, using choice), and, conversely, Lean's type theory has set-theoretic models in which types are sets, assuming as many inaccessible cardinals as there are universes in use (Werner 1997 for the calculus of inductive constructions; Carneiro 2019 for Lean). Everyday mathematics transfers easily between the two; differences show up in what can even be said (no global ) and in which proofs carry computational content. Compare ZFC.
History and global context
- Bertrand Russell introduced the theory of types (1908), developed with Alfred North Whitehead in Principia Mathematica (1910–13), to avoid the paradoxes. Alonzo Church simplified it to the simple theory of types in 1940.
- L. E. J. Brouwer founded intuitionism (from 1907); Arend Heyting (Netherlands, 1930) and Andrey Kolmogorov (Soviet Union, 1932) gave the proof interpretation of the connectives; Valery Glivenko (1929) proved his double-negation theorem; Gerhard Gentzen invented natural deduction (1935) and Dag Prawitz (Sweden) proved its normalisation (1965); Saul Kripke gave his semantics in 1965.
- Haskell Curry noticed that the types of combinators are axioms of implicational logic (1934; with Robert Feys, 1958). William Howard's notes "The formulae-as-types notion of construction" circulated from 1969 and were published in 1980. N. G. de Bruijn's Automath project (Netherlands, from 1967) was the first proof checker built on these ideas.
- Per Martin-Löf (Sweden) developed intuitionistic type theory with dependent types from the early 1970s. Jean-Yves Girard (France) introduced System F and found Girard's paradox (1972), and John Reynolds independently found the polymorphic λ-calculus (1974). Thierry Coquand and Gérard Huet created the calculus of constructions (1985–88), extended with inductive types by Coquand and Christine Paulin-Mohring (1990) — the basis of Coq/Rocq and of Lean.
- Leonardo de Moura started Lean at Microsoft Research in 2013; Lean 4 (with Sebastian Ullrich) followed in 2021. Vladimir Voevodsky's univalent foundations (from 2009) connected type theory with homotopy theory.
Further reading
- Rob Nederpelt and Herman Geuvers, Type Theory and Formal Proof: An Introduction (2014) — from the simply typed λ-calculus to the calculus of constructions, step by step.
- Jean-Yves Girard, Yves Lafont and Paul Taylor, Proofs and Types (1989) — Curry–Howard, normalisation and System F.
- Morten Heine Sørensen and Paweł Urzyczyn, Lectures on the Curry–Howard Isomorphism (2006) — intuitionistic logic, Kripke models and typed calculi with full proofs.
- Jeremy Avigad, Leonardo de Moura, Soonho Kong and Sebastian Ullrich, Theorem Proving in Lean 4 — the official introduction to Lean's dependent type theory.
- Per Martin-Löf, Intuitionistic Type Theory (1984) — notes of lectures given in Padua, the classic statement of the theory.
- The Univalent Foundations Program, Homotopy Type Theory: Univalent Foundations of Mathematics (2013).
- Mario Carneiro, The Type Theory of Lean (MSc thesis, Carnegie Mellon University, 2019) — the metatheory of Lean's logic.