Skip to content
The Lumenary
Advanced undergraduateUndergraduate 120 min

Type theory and propositions as types

A proof is a program and a proposition is the type of its proofs — the idea behind Lean, and a foundation for mathematics quite unlike set theory.

Formally verified · 34 LeanComputationally tested · 2 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review
MotivationH

What is a proof?

Here is a complete, machine-checked Lean proof that AA implies "BB implies AA":

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 AA, ignores a proof of BB, 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.

IntuitionH

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∧BA \wedge Ba pair: a proof of AA and a proof of BBproduct A×BA \times B
A∨BA \vee Ba proof of AA or a proof of BB, labelled with whichsum A+BA + B
A→BA \to Ba method turning any proof of AA into a proof of BBfunction type A→BA \to B
⊤\toptrivialunit type 11
⊥\botimpossible: there is noneempty type 00
¬A\neg Aa method turning a proof of AA into a proof of ⊥\botA→0A \to 0
∀x:X.  P(x)\forall x{:}X.\; P(x)a function sending each xx to a proof of P(x)P(x)Π\Pi-type ∏x:XP(x)\prod_{x : X} P(x)
∃x:X.  P(x)\exists x{:}X.\; P(x)a witness xx together with a proof of P(x)P(x)Σ\Sigma-type ∑x:XP(x)\sum_{x : X} P(x)

Read this way, a proof of A∨BA \vee B must decide which disjunct holds, and a proof of ∃x. P(x)\exists x.\, P(x) must produce a witness. That is why excluded middle, A∨¬AA \vee \neg A, 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.

DefinitionsH

Definitions

Definition— typing judgement

A typing judgement Γ⊢t:A\Gamma \vdash t : A asserts that the term tt has type AA in the context Γ=x1:A1,…,xn:An\Gamma = x_1 : A_1, \dots, x_n : A_n, 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 p,q,…p, q, \dots, and A→BA \to B, A×BA \times B, A+BA + B, 11, 00. The typing rules come in pairs, an introduction rule that builds a term of a type and an elimination rule that uses one:

(x:A)∈ΓΓ⊢x:AΓ,x:A⊢t:BΓ⊢λx:A. t:A→BΓ⊢s:A→BΓ⊢u:AΓ⊢s u:B\dfrac{(x : A) \in \Gamma}{\Gamma \vdash x : A} \qquad \dfrac{\Gamma, x : A \vdash t : B}{\Gamma \vdash \lambda x{:}A.\, t : A \to B} \qquad \dfrac{\Gamma \vdash s : A \to B \quad \Gamma \vdash u : A}{\Gamma \vdash s\, u : B}Γ⊢s:AΓ⊢t:BΓ⊢(s,t):A×BΓ⊢p:A×BΓ⊢π1 p:AΓ⊢π2 p:B\dfrac{\Gamma \vdash s : A \quad \Gamma \vdash t : B}{\Gamma \vdash (s, t) : A \times B} \qquad \dfrac{\Gamma \vdash p : A \times B}{\Gamma \vdash \pi_1\, p : A \qquad \Gamma \vdash \pi_2\, p : B}Γ⊢s:AΓ⊢inl s:A+BΓ⊢t:BΓ⊢inr t:A+BΓ⊢e:A+BΓ,x:A⊢s:CΓ,y:B⊢t:CΓ⊢case  e  of  inl x⇒s∣inr y⇒t:C\dfrac{\Gamma \vdash s : A}{\Gamma \vdash \mathsf{inl}\, s : A + B} \qquad \dfrac{\Gamma \vdash t : B}{\Gamma \vdash \mathsf{inr}\, t : A + B} \qquad \dfrac{\Gamma \vdash e : A + B \quad \Gamma, x : A \vdash s : C \quad \Gamma, y : B \vdash t : C}{\Gamma \vdash \mathsf{case}\; e\; \mathsf{of}\; \mathsf{inl}\, x \Rightarrow s \mid \mathsf{inr}\, y \Rightarrow t : C}

together with Γ⊢∗:1\Gamma \vdash \ast : 1 and Γ⊢e:0Γ⊢abort e:C\dfrac{\Gamma \vdash e : 0}{\Gamma \vdash \mathsf{abort}\, e : C}. Computation rules say that eliminating what was just introduced cancels: (λx. t) u→t[u/x](\lambda x.\, t)\, u \to t[u/x], π1(s,t)→s\pi_1(s, t) \to s, and case\mathsf{case} applied to inl s\mathsf{inl}\, s 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: →\to-introduction discharges a hypothesis, →\to-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 B:A→TypeB : A \to \mathsf{Type} assigns a type B(a)B(a) to each a:Aa : A — for example Vec(n)\mathrm{Vec}(n), the lists of length exactly nn, or the proposition n<mn < m for fixed nn. An element of the dependent function type ∏x:AB(x)\prod_{x : A} B(x) sends each a:Aa : A to an element of B(a)B(a) — the output type depends on the input. An element of the dependent pair type ∑x:AB(x)\sum_{x : A} B(x) is a pair (a,b)(a, b) with a:Aa : A and b:B(a)b : B(a). When BB is a family of propositions, ∏\prod is ∀\forall and ∑\sum is ∃\exists. When BB is constant, ∏x:AB=A→B\prod_{x:A} B = A \to B and ∑x:AB=A×B\sum_{x:A} B = A \times B.

Definition— inductive type

An inductive type is freely generated by its constructors. The natural numbers N\N have constructors zero:N\mathsf{zero} : \N and succ:N→N\mathsf{succ} : \N \to \N; lists over AA have nil\mathsf{nil} and cons:A→List(A)→List(A)\mathsf{cons} : A \to \mathrm{List}(A) \to \mathrm{List}(A). Each comes with a recursor (eliminator). For N\N:

rec:∏P:N→TypeP(zero)→(∏n:NP(n)→P(succ n))→∏n:NP(n),\mathsf{rec} : \prod_{P : \N \to \mathsf{Type}} P(\mathsf{zero}) \to \Big(\prod_{n : \N} P(n) \to P(\mathsf{succ}\, n)\Big) \to \prod_{n : \N} P(n),

with computation rules rec P z s zero=z\mathsf{rec}\, P\, z\, s\, \mathsf{zero} = z and rec P z s (succ n)=s n (rec P z s n)\mathsf{rec}\, P\, z\, s\, (\mathsf{succ}\, n) = s\, n\, (\mathsf{rec}\, P\, z\, s\, n). With PP constant it defines functions by recursion; with PP a family of propositions it is the principle of induction.

Definition— universes

Types are terms too, so they need types: universes Type0:Type1:Type2:⋯\mathsf{Type}_0 : \mathsf{Type}_1 : \mathsf{Type}_2 : \cdots. Lean also has the universe Prop\mathsf{Prop} of propositions, in which any two proofs of the same proposition are equal (proof irrelevance). A single universe containing itself, Type:Type\mathsf{Type} : \mathsf{Type}, makes the theory inconsistent (Girard's paradox, 1972) — a type-theoretic echo of Russell's paradox.

Worked examplesH

Examples: proofs as programs

Example— Three classical combinators are three tautologies

  • I=λx:A. x:A→AI = \lambda x{:}A.\, x : A \to A proves A→AA \to A.
  • K=λx:A. λy:B. x:A→B→AK = \lambda x{:}A.\, \lambda y{:}B.\, x : A \to B \to A proves A→(B→A)A \to (B \to A).
  • S=λf. λg. λx. f x (g x):(A→B→C)→(A→B)→A→CS = \lambda f.\, \lambda g.\, \lambda x.\, f\, x\, (g\, x) : (A \to B \to C) \to (A \to B) \to A \to C.

The typing derivation of SS is a natural-deduction proof: assume f:A→B→Cf : A \to B \to C, g:A→Bg : A \to B, x:Ax : A; then f x:B→Cf\, x : B \to C and g x:Bg\, x : B by modus ponens, so f x (g x):Cf\, x\, (g\, x) : C; discharge the three assumptions. The composition λf g x. f (g x):(B→C)→(A→B)→A→C\lambda f\, g\, x.\, f\,(g\, x) : (B \to C) \to (A \to B) \to A \to C is the transitivity of implication.

Example— Negation and double negation

With ¬A=A→0\neg A = A \to 0:

  • λa. λk. k a:A→¬¬A\lambda a.\, \lambda k.\, k\, a : A \to \neg\neg A (given aa, any refutation kk of AA yields ⊥\bot).
  • λh. λa. h (λk. k a):¬¬¬A→¬A\lambda h.\, \lambda a.\, h\,(\lambda k.\, k\, a) : \neg\neg\neg A \to \neg A.
  • λf. λk. λa. k (f a):(A→B)→(¬B→¬A)\lambda f.\, \lambda k.\, \lambda a.\, k\,(f\, a) : (A \to B) \to (\neg B \to \neg A), contraposition.
  • λh. h (inr (λa. h (inl a))):¬¬(A+¬A)\lambda h.\, h\,(\mathsf{inr}\,(\lambda a.\, h\,(\mathsf{inl}\, a))) : \neg\neg(A + \neg A): excluded middle cannot be refuted.

But no closed term has type ¬¬A→A\neg\neg A \to A, or A+¬AA + \neg A, or Peirce's law ((A→B)→A)→A((A \to B) \to A) \to A, for a base type AA — although all three are classical tautologies. Theorem 4 proves this for Peirce's law.

Example— Products and functions: currying

λf a b. f (a,b):(A×B→C)→(A→B→C)\lambda f\, a\, b.\, f\,(a, b) : (A \times B \to C) \to (A \to B \to C) and λg p. g (π1 p) (π2 p):(A→B→C)→(A×B→C)\lambda g\, p.\, g\,(\pi_1\, p)\,(\pi_2\, p) : (A \to B \to C) \to (A \times B \to C). Logically: "A∧BA \wedge B implies CC" is equivalent to "AA implies that BB implies CC". The two terms are mutually inverse up to βη-equality — a type isomorphism, not merely a logical equivalence.

Example— Dependent types

  • λn. (n+1,proof that n<n+1):∏n:N∑m:Nn<m\lambda n.\, (n + 1, \text{proof that } n < n + 1) : \prod_{n : \N} \sum_{m : \N} n < m proves ∀n ∃m. n<m\forall n\, \exists m.\, n < m — and you can run it to compute a witness.
  • ∑n:N(n>0)\sum_{n : \N} (n > 0) is the type of positive natural numbers: a number paired with a certificate.
  • append:∏m,n:NVec(m)→Vec(n)→Vec(m+n)\mathrm{append} : \prod_{m, n : \N} \mathrm{Vec}(m) \to \mathrm{Vec}(n) \to \mathrm{Vec}(m + n): 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 N\N by recursion on the second argument: m+zero=mm + \mathsf{zero} = m, m+succ n=succ(m+n)m + \mathsf{succ}\, n = \mathsf{succ}(m + n). Then m+0=mm + 0 = m holds by computation, but 0+n=n0 + n = n does not — it needs induction: 0+zero=zero0 + \mathsf{zero} = \mathsf{zero}, and if 0+n=n0 + n = n then 0+succ n=succ(0+n)=succ n0 + \mathsf{succ}\, n = \mathsf{succ}(0 + n) = \mathsf{succ}\, n. Both the definition and the proof are applications of the same recursor, with different PP.

ProofH

The main theorems

We work with the implicational fragment — types built from base types p0,p1,…p_0, p_1, \dots by →\to — where everything can be proved completely (and is, in Lean); the extensions to ×\times, ++, 11, 00 add more cases of the same kind.

Natural deduction Γ⊢A\Gamma \vdash A has three rules: if A∈ΓA \in \Gamma then Γ⊢A\Gamma \vdash A; from Γ,A⊢B\Gamma, A \vdash B infer Γ⊢A→B\Gamma \vdash A \to B; from Γ⊢A→B\Gamma \vdash A \to B and Γ⊢A\Gamma \vdash A infer Γ⊢B\Gamma \vdash B.

Theorem— Theorem 1 (Curry–Howard)

For every context Γ=A1,…,An\Gamma = A_1, \dots, A_n and type AA:   Γ⊢A\;\Gamma \vdash A is derivable in natural deduction if and only if there is a term tt with x1:A1,…,xn:An⊢t:Ax_1 : A_1, \dots, x_n : A_n \vdash t : A.

Proof

(⇐\Leftarrow) By induction on the typing derivation, erase the terms: a variable xi:Aix_i : A_i becomes the hypothesis Ai∈ΓA_i \in \Gamma; an abstraction becomes →\to-introduction; an application becomes →\to-elimination.

(⇒\Rightarrow) By induction on the natural-deduction derivation, build terms: a hypothesis A=AiA = A_i becomes the variable xix_i; if Γ,A⊢B\Gamma, A \vdash B is realised by tt (with a new variable x:Ax : A), then Γ⊢A→B\Gamma \vdash A \to B is realised by λx:A. t\lambda x{:}A.\, t; if Γ⊢A→B\Gamma \vdash A \to B and Γ⊢A\Gamma \vdash A are realised by ss and uu, then Γ⊢B\Gamma \vdash B is realised by s us\, u.

∎

The correspondence goes further than provability. A detour in a proof — introduce A→BA \to B by discharging AA, then immediately apply it to a proof of AA — is a β-redex (λx. t) u(\lambda x.\, t)\, u, and removing it means substituting the proof uu for the hypothesis xx, which is β-reduction. Types are preserved by reduction (subject reduction: if Γ⊢t:A\Gamma \vdash t : A and t→t′t \to t' then Γ⊢t′:A\Gamma \vdash t' : A, 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 Γ\Gamma and types A,TA, T is Γ⊢λx:A. x x:T\Gamma \vdash \lambda x{:}A.\, x\, x : T.

Proof

A typing of x xx\, x under x:Ax : A requires x:C→Bx : C \to B and x:Cx : C for some B,CB, C, so A=C→BA = C \to B and A=CA = C, giving C=C→BC = C \to B. That is impossible: by induction on CC, no type equals a function type out of itself (C→BC \to B has more symbols than CC).

∎

So the terms Ω\Omega and YY 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 (W,≤)(W, \le) of states of knowledge together with, for each state ww, a set of atoms known at ww, such that knowledge persists: if w≤vw \le v and pp is known at ww, then pp is known at vv. Forcing is defined by recursion on types: w⊩pw \Vdash p if pp is known at ww, and

w⊩A→B  ⟺  for every v≥w, v⊩A implies v⊩B.w \Vdash A \to B \quad\iff\quad \text{for every } v \ge w,\ v \Vdash A \text{ implies } v \Vdash B .

Theorem— Theorem 3 (soundness of natural deduction for Kripke models)

(a) Persistence: if w≤vw \le v and w⊩Aw \Vdash A then v⊩Av \Vdash A. (b) If Γ⊢A\Gamma \vdash A and w⊩Bw \Vdash B for every B∈ΓB \in \Gamma, then w⊩Aw \Vdash A.

Proof

(a) Induction on AA. For atoms this is the persistence condition. For A→BA \to B: if w⊩A→Bw \Vdash A \to B and v≥wv \ge w, then every u≥vu \ge v also satisfies u≥wu \ge w (transitivity), so u⊩Au \Vdash A implies u⊩Bu \Vdash B; hence v⊩A→Bv \Vdash A \to B.

(b) Induction on the derivation, for all states ww at once. Hypothesis rule: immediate. →\to-introduction: suppose Γ,A⊢B\Gamma, A \vdash B and ww forces all of Γ\Gamma. Let v≥wv \ge w with v⊩Av \Vdash A. By (a), vv forces all of Γ\Gamma, so vv forces all of Γ,A\Gamma, A, and the induction hypothesis gives v⊩Bv \Vdash B. Hence w⊩A→Bw \Vdash A \to B. →\to-elimination: from w⊩A→Bw \Vdash A \to B and w⊩Aw \Vdash A, apply the definition with v=wv = w (reflexivity) to get w⊩Bw \Vdash B.

∎

Theorem— Theorem 4 (Peirce's law is uninhabited)

Let p,qp, q be distinct base types. There is no closed term tt with ⊢t:((p→q)→p)→p\vdash t : ((p \to q) \to p) \to p, although this type is a classical tautology.

Proof

Take two states w0≤w1w_0 \le w_1, with pp known only at w1w_1 and qq known nowhere.

  • w0⊮p→qw_0 \not\Vdash p \to q: at the later state w1w_1, pp holds and qq does not. Similarly w1⊮p→qw_1 \not\Vdash p \to q.
  • Hence w0⊩(p→q)→pw_0 \Vdash (p \to q) \to p: at w0w_0 and at w1w_1 the hypothesis p→qp \to q is not forced, so the implication holds vacuously.
  • But w0⊮pw_0 \not\Vdash p. So w0⊮((p→q)→p)→pw_0 \not\Vdash ((p \to q) \to p) \to p, taking v=w0v = w_0 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 pp is true the conclusion holds, and if pp is false then p→qp \to q is true, so (p→q)→p(p \to q) \to p forces pp to be true — a contradiction, so that case cannot occur.

∎

With forcing extended by w⊩A∨Bw \Vdash A \vee B iff w⊩Aw \Vdash A or w⊩Bw \Vdash B, and ⊥\bot forced nowhere, the same two-state model refutes excluded middle: w0⊮pw_0 \not\Vdash p, and w0⊮¬pw_0 \not\Vdash \neg p because pp becomes known at w1w_1. Conversely, Glivenko's theorem (1929) says that for every classical propositional tautology AA, the double negation ¬¬A\neg\neg A 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 N\N and its recursor, the induction principle

∏P:N→PropP(zero)→(∏nP(n)→P(succ n))→∏nP(n)\prod_{P : \N \to \mathsf{Prop}} P(\mathsf{zero}) \to \Big(\prod_{n} P(n) \to P(\mathsf{succ}\, n)\Big) \to \prod_{n} P(n)

is inhabited, by the recursor itself. Moreover the constructors are injective and have disjoint images, so N\N is freely generated by them. The same holds for lists, with the principle "P(nil)P(\mathsf{nil}) and P(l)→P(cons a l)P(l) \to P(\mathsf{cons}\, a\, l) imply P(l)P(l) for all ll".

Proof

Induction is rec\mathsf{rec} with motive PP. For disjointness, define by recursion isZero:N→Prop\mathsf{isZero} : \N \to \mathsf{Prop} with isZero(zero)=⊤\mathsf{isZero}(\mathsf{zero}) = \top and isZero(succ n)=⊥\mathsf{isZero}(\mathsf{succ}\, n) = \bot; if succ n=zero\mathsf{succ}\, n = \mathsf{zero}, transporting the proof of ⊤=isZero(zero)\top = \mathsf{isZero}(\mathsf{zero}) along this equality gives a proof of ⊥\bot. For injectivity, define pred\mathsf{pred} by recursion with pred(succ n)=n\mathsf{pred}(\mathsf{succ}\, n) = n; if succ m=succ n\mathsf{succ}\, m = \mathsf{succ}\, n then m=pred(succ m)=pred(succ n)=nm = \mathsf{pred}(\mathsf{succ}\, m) = \mathsf{pred}(\mathsf{succ}\, n) = n. Lists are identical in structure.

∎

Note how much the recursor does: in ZFC, N\N is constructed as a set (the least inductive set), and induction is a theorem about it; in type theory, N\N 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 A,BA, B and a relation R:A→B→TypeR : A \to B \to \mathsf{Type}, the type

(∏x:A∑y:BR(x,y))→∑f:A→B∏x:AR(x,f x)\Big(\prod_{x : A} \sum_{y : B} R(x, y)\Big) \to \sum_{f : A \to B} \prod_{x : A} R(x, f\, x)

is inhabited, without any axiom.

Proof

The term λh. (λx. π1(h x),  λx. π2(h x))\lambda h.\, \big(\lambda x.\, \pi_1(h\, x),\; \lambda x.\, \pi_2(h\, x)\big) has this type: hh assigns to each xx a pair, and the choice function is its first component.

∎

This explains a famous difference. In type theory a proof of ∀x ∃y R\forall x\, \exists y\, R built from Σ already is a choice function. Lean, however, puts ∃\exists in Prop\mathsf{Prop}, 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 00: 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 Prop\mathsf{Prop}, 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).

Formal mathematicsF

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.

k_combinatorLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
s_combinatorLean 4Proved here from basic factsFormally verified
/-- 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)
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
and_comm_termLean 4Proved here from basic factsFormally verified
/-- 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⟩
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
or_elim_termLean 4Proved here from basic factsFormally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
not_not_emLean 4Proved here from basic factsFormally verified
/-- `¬¬(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))
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
peirce_classicalLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/TypeTheory.leanTry it in the Lean web editor

Π 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.

exists_largerLean 4Proved here from basic factsFormally verified
/-- 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⟩
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
ttChoiceLean 4Formally verified
/-- **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⟩
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
prop_choiceLean 4Applies a library theoremFormally verified
/-- 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 h
Axioms used: Classical.choiceformal/Lumenary/TypeTheory.leanTry it in the Lean web editor

Inductive types (Theorem 5). A home-made copy of the natural numbers: the induction principle is literally N.rec with the predicate as motive, 0+n=n0 + n = n is proved with it, and freeness comes from the recursor. Then lists.

NLean 4Formally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
inductionLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
addLean 4Formally verified
/-- 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)
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
zero_addLean 4Proved here from basic factsFormally verified
/-- `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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
succ_ne_zeroLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
succ_injectiveLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
LstLean 4Formally verified
/-- Lists over `α` as an inductive type, freely generated by `nil` and `cons`. -/
inductive Lst (α : Type) where
  | nil : Lst α
  | cons : α → Lst α → Lst α
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
inductionLean 4Proved here from basic factsFormally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
length_appendLean 4Proved here from basic factsFormally verified
/-- `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]
    omega
Axioms used: propext, Quot.soundformal/Lumenary/TypeTheory.leanTry it in the Lean web editor

Curry–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 SS is built rule by rule, and the two directions of Theorem 1 are proved by induction on derivations.

TyLean 4Formally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
TmLean 4Formally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
HasTypeLean 4Formally verified
/-- 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)
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
DerivableLean 4Formally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
s_typedLean 4Proved here from basic factsFormally verified
/-- 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)))))
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
derivableLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
exists_termLean 4Proved here from basic factsFormally verified
/-- **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⟩
Axioms used: propext, Quot.soundformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
curry_howardLean 4Proved here from basic factsFormally verified
/-- **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⟩
Axioms used: propext, Quot.soundformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
self_application_untypableLean 4Proved here from basic factsFormally verified
/-- **`λ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₁
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor

Kripke models and Peirce's law (Theorems 3 and 4).

KripkeModelLean 4Formally verified
/-- 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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
ForcesLean 4Formally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
forces_monoLean 4Proved here from basic factsFormally verified
/-- **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
Axioms used: No axiomsformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
soundLean 4Proved here from basic factsFormally verified
/-- **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Γ)
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
peirceModelLean 4Formally verified
/-- 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
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
peirce_not_derivableLean 4Proved here from basic factsFormally verified
/-- **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)
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor
peirce_uninhabitedLean 4Proved here from basic factsFormally verified
/-- **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.derivable
Axioms used: propextformal/Lumenary/TypeTheory.leanTry it in the Lean web editor

There 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.

ComputationC

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.

Type inference by unification: principal types as theoremsPythonExact computationComputationally tested
"""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()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

ω=λx. x x\omega = \lambda x.\, x\, x and YY fail the occurs check — the equation t0=t0→t1t_0 = t_0 \to t_1 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.

Inhabited types versus tautologies: proof search and truth tablesPythonExact computationExhaustive finite checkComputationally tested
"""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()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

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 102102 implicational formulas in p,qp, q with at most four atoms, 4848 are tautologies and 4646 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

Explore: a tautology with no proof term

Check in the truth table that Peirce's law is a classical tautology. Then compare it with p→¬¬pp \to \neg\neg p 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).

Try:

Tautology — true in all 4 rows.

pqp → q(p → q) → p((p → q) → p) → p
TTTTT
TFFTT
FTTFT
FFTFT

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.

laterw₀w₁p: no q: nop: yes q: no
The two-state Kripke model refuting Peirce's law. At w₀ nothing is known; at w₁ the atom p is known. Since p → q fails at w₁, it fails at w₀ too, so (p → q) → p holds vacuously at w₀ — but p does not.

Try it: decide at which states p→qp \to q, (p→q)→p(p \to q) \to p, ¬p\neg p and ¬¬p\neg\neg p are forced (with ⊥\bot forced nowhere). You should find that ¬¬p\neg\neg p is forced at w0w_0 although pp is not — the model refutes ¬¬p→p\neg\neg p \to p as well.

Exercises

Exercises

1. Type theory and propositions as types

Warm-upHuman

Under the propositions-as-types correspondence, what is a proof of A∧BA \wedge B?

Choose one

2. Type theory and propositions as types

Warm-upHuman

What is the most general simple type of λf. λg. λx. f (g x)\lambda f.\, \lambda g.\, \lambda x.\, f\,(g\, x), and hence which tautology does it prove?

Choose one

3. Type theory and propositions as types

CoreHuman

Which of these types are inhabited by a closed term (equivalently, are theorems of intuitionistic propositional logic)? Here p,qp, q are base types and ¬A=A→0\neg A = A \to 0. Choose all that apply.

Choose all that apply

4. Type theory and propositions as types

CoreHuman

In the two-state Kripke model w0≤w1w_0 \le w_1 with pp known only at w1w_1, which formulas are forced at w0w_0? (Rules: w⊩A→Bw \Vdash A \to B iff every v≥wv \ge w forcing AA forces BB; ⊥\bot is forced nowhere, ¬A=A→⊥\neg A = A \to \bot; w⊩A∨Bw \Vdash A \vee B iff w⊩Aw \Vdash A or w⊩Bw \Vdash B.) Choose all that apply.

Choose all that apply

5. Type theory and propositions as types

CoreHuman

Let AA be a base type. How many closed β-normal terms of type A→A→AA \to A \to A are there (counting α-equivalent terms once)?

6. Type theory and propositions as types

CoreHuman

In the proof of soundness for Kripke models (Theorem 3), the →\to-introduction case goes: assume ww forces all of Γ\Gamma, let v≥wv \ge w force AA; then vv forces all of Γ,A\Gamma, A, so v⊩Bv \Vdash B by induction. Which fact justifies 'vv forces all of Γ\Gamma'?

Choose one

7. Type theory and propositions as types

CoreHuman

With addition on N\N defined by m+zero=mm + \mathsf{zero} = m and m+succ n=succ(m+n)m + \mathsf{succ}\, n = \mathsf{succ}(m + n), why is n+0=nn + 0 = n true by computation while 0+n=n0 + n = n needs induction?

Choose one

8. Type theory and propositions as types

StretchFormal

The 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?

Choose one

9. Type theory and propositions as types

StretchComputational

Write 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 u≤vu \le v (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.

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Common misconceptionsH

Common misconceptions

Misconception

"Type theory and set theory are interchangeable." They have different primitive notions. In ZFC everything is a set and membership x∈yx \in y 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 t:At : A, 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 ({1}∈{{1}}\{1\} \in \{\{1\}\} and {1}∈{{1},2}\{1\} \in \{\{1\}, 2\}), 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, 2∈32 \in 3 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 ⊥\bot. 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.

ViewpointsH

Viewpoints

Logic— Intuitionism and the double-negation translation

For Brouwer, mathematics was a mental construction, and a proof of ∃x. P(x)\exists x.\, P(x) that produces no xx 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 ∧\wedge, ∨\vee as intersection and union, U→VU \to V as the interior of (X∖U)∪V(X \setminus U) \cup V, and ¬U\neg U as the interior of the complement. On R\R, take p=(0,∞)p = (0, \infty): then ¬p=(−∞,0)\neg p = (-\infty, 0) and p∨¬p=R∖{0}p \vee \neg p = \R \setminus \{0\}, which is not all of R\R — excluded middle fails at the boundary point 00. Every intuitionistic propositional theorem denotes the whole space, and the propositional formulas true in all such interpretations over R\R are exactly the intuitionistic theorems. In homotopy type theory the picture deepens: types are spaces, terms are points, proofs of a=ba = b 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 a→ba \to b characterised by c≤(a→b)  ⟺  c∧a≤bc \le (a \to b) \iff c \wedge a \le b. Formulas modulo provable equivalence form the free Heyting algebra on the atoms (the Lindenbaum algebra), and a formula is provable exactly when it equals 11 in every Heyting algebra. A three-element chain 0<12<10 < \tfrac12 < 1 already refutes Peirce's law: with p=12p = \tfrac12, q=0q = 0, one computes p→q=0p \to q = 0, (p→q)→p=1(p \to q) \to p = 1, and ((p→q)→p)→p=12≠1((p \to q) \to p) \to p = \tfrac12 \ne 1. Boolean algebras are exactly the Heyting algebras satisfying a∨¬a=1a \vee \neg a = 1.

Computation— Proof assistants and certified programs

Dependent types let a program carry its specification: a sorting function of type ∏l∑l′Sorted(l′)×Perm(l,l′)\prod_{l} \sum_{l'} \mathrm{Sorted}(l') \times \mathrm{Perm}(l, l') 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 λ\lambda is the adjunction Hom(A×B,C)≅Hom(A,CB)\Hom(A \times B, C) \cong \Hom(A, C^B). Dependent types correspond to locally cartesian closed categories (Seely, 1984), where a type family over AA is an object of the slice category over AA and Σ\Sigma and Π\Pi are the left and right adjoints of pulling back. Toposes add a subobject classifier — a type of propositions. See categorical logic.

Set-theoretic connectionH

Types and sets: two foundations

ZFCLean's type theory
primitive notionmembership x∈yx \in y, one global relationthe typing judgement t:At : A
what objects aresets, all of one kindterms, each with its type
logicclassical first-order logic, underneath the axiomsbuilt in: propositions are types
natural numbersconstructed: 0=∅0 = \varnothing, n+1=n∪{n}n + 1 = n \cup \{n\}an inductive type, with induction as its eliminator
functionssets of ordered pairsprimitive (λ\lambda-terms)
choicethe axiom of choiceClassical.choice, an explicit axiom
paradox controlseparation instead of comprehensionuniverses Type0:Type1:⋯\mathsf{Type}_0 : \mathsf{Type}_1 : \cdots

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 ∈\in) and in which proofs carry computational content. Compare ZFC.

History & global context

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

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.