Skip to main content

ProofsPath · 25 min

The Curry-Howard Correspondence

Important
For:GeneralResearch

Why This Matters

The Curry-Howard correspondence is the single observation that makes dependent-type theorem provers possible. It identifies, structurally, the syntax of intuitionistic logic with the syntax of typed lambda calculus. Under the identification:

  • Propositions correspond to types.
  • Proofs of a proposition correspond to inhabitants (terms) of the corresponding type.
  • Logical connectives correspond to type constructors: conjunction becomes the product type, disjunction becomes the sum type, implication becomes the function type, universal quantification becomes the dependent product, existential becomes the dependent sum.
  • Proof composition (modus ponens) corresponds to function application.

Under this view, checking a proof is type-checking a term. Lean's kernel does not need a separate "proof checker": it only has a type checker, applied to the type universe Prop where the propositions live. That economy of mechanism is the reason a few-thousand-line kernel can verify proofs of theorems whose paper version runs to dozens of pages.

The correspondence reaches further. It identifies propositional logic with simply-typed lambda calculus, first-order logic with dependent types, second-order logic with System F, and so on. Lean 4 sits in the dependent-type rung, which is the right level for most mathematics.

The Translation Table

LogicType theoryLean 4 syntax
Proposition PPType in PropP : Prop
Proof of PPInhabitant of PPh : P
P    QP \implies QFunction type PQP \to QP → Q
PQP \land QProduct type P×QP \times QP ∧ Q (sugar for And P Q)
PQP \lor QSum type P+QP + QP ∨ Q (sugar for Or P Q)
x:A,P(x)\forall x : A, P(x)Dependent function Πx:AP(x)\Pi_{x : A} P(x)(x : A) → P x
x:A,P(x)\exists x : A, P(x)Dependent pair Σx:AP(x)\Sigma_{x : A} P(x)∃ x : A, P x
¬P\neg PPP \to \bot¬ P (sugar for P → False)
\bot (False)Empty typeFalse
\top (True)Unit typeTrue

Implication-elimination (modus ponens) is function application: from h : P → Q and hP : P, the term h hP : Q is a proof of QQ.

And-introduction is pairing: from hP : P and hQ : Q, the term ⟨hP, hQ⟩ : P ∧ Q.

Or-introduction is injection: from hP : P, the term Or.inl hP : P ∨ Q for any Q.

Existence-introduction is also a pair: from a witness a : A and hP : P a, the term ⟨a, hP⟩ : ∃ x, P x.

Why This Is Not a Metaphor

The correspondence is a structural identity in the formal sense: there is a bijection between proofs and terms that preserves the operations on both sides. The bijection is not an analogy; the kernel does not first translate "logical proof" into "typed term" and then check the term. The kernel has only ever dealt with typed terms. Propositions and proofs are objects in the language that the kernel was already designed to type-check.

The consequence: if you can write a term of type P, you have a proof of P, period. Conversely, if P is provable, you can in principle write down the term. The "in principle" carries a complexity caveat: minimum proof length can be very large: but no semantic gap.

Intuitionistic by Default, Classical by Axiom

The correspondence as stated above is for intuitionistic logic. Lean's type theory, like Coq's and Agda's, is intuitionistic by default. The law of excluded middle (P¬PP \lor \neg P) is not a theorem; you cannot construct a term of this type for arbitrary P.

Two ways to recover classical reasoning:

Decidability. For each P : Prop, an instance of Decidable P provides a term of type P ⊕ ¬ P. Decidability is provable for many specific propositions (equality of natural numbers, comparison of integers, membership in a finite set). Working with decidable propositions, classical-looking reasoning is constructive.

The Classical.choice axiom. Lean ships with three axioms: propositional extensionality, quotient soundness, and Classical.choice. The latter says: for any non-empty type, an inhabitant can be chosen. From Classical.choice plus propext you get Classical.em : ∀ P : Prop, P ∨ ¬ P: the law of excluded middle, derivable but axiomatic.

Mathlib uses classical reasoning extensively (the standard ϵ\epsilon-δ\delta definitions, the axiom of choice in topology and measure theory). A proof in Mathlib generally depends on Classical.choice; constructive Mathlib is a smaller fragment. #print axioms shows which axioms a specific theorem uses; see sorry-free discipline and axiom auditing.

Worked Translation: Modus Ponens

Paper statement: "If PP implies QQ and PP holds, then QQ holds."

Lean type:

theorem modus_ponens (P Q : Prop) (h : P → Q) (hP : P) : Q := h hP

The proof term is h hP: function application. The kernel checks: h has type P → Q, hP has type P, the result of applying h to hP has type Q. Match.

In tactic mode the same proof is:

theorem modus_ponens (P Q : Prop) (h : P → Q) (hP : P) : Q := by
  exact h hP

The exact tactic accepts a term and uses it directly. Underneath, Lean is doing the same kernel check.

Worked Translation: Conjunction Elimination

Paper statement: "If PQP \land Q holds, then PP holds."

Lean type:

theorem and_left (P Q : Prop) (h : P ∧ Q) : P := h.left

The proof term is h.left: project the first component of the pair. The kernel knows And is defined as a structure with fields left : P and right : Q, so the projection has the right type. In paper math we would write "destructure hh as hP,hQ\langle hP, hQ \rangle and return hPhP"; the term h.left is that destructuring, compiled to direct projection.

Common Misconceptions

Watch Out

The correspondence is not 'proofs are like programs'

The word "are" is technical. Proofs and programs are the same syntactic objects, viewed under different conventions. The thing the kernel processes is a typed term; whether we read it as a proof or as a program is up to the reader. This is why "checking a proof" and "type-checking a program" are literally the same algorithm in Lean.

Watch Out

Curry-Howard does not require classical logic

The base correspondence is between intuitionistic propositional logic and simply-typed lambda calculus. Classical logic requires an additional axiom: Classical.choice in Lean, or equivalently the law of excluded middle. Constructive mathematics is the proper fragment for which the correspondence is the "obvious" reading.

Watch Out

Not all terms are useful proofs

A term of type P → P can be fun h => h (the identity function), fun h => h again, or any longer expression that reduces to the identity. Many distinct terms inhabit the same proposition; the kernel only cares that some term inhabits it. The choice of term sometimes matters for proof readability or definitional reduction, but propositional content is invariant under choice of inhabitant. The phrase "proof irrelevance" formalizes this: in Prop, all proofs of the same proposition are definitionally equal.

Watch Out

The correspondence is operational, not just notational

You can run a Lean proof term as a program. The reduction rules of the type theory (beta-reduction for functions, iota-reduction for pattern matching) are the operational semantics. A proof of PQP \land Q that says "construct a pair and project the left coordinate" reduces to "return hPhP" by the iota-reduction rule. This computational content is what powers rfl (definitional equality) and decide (executing a decision procedure to discharge a proposition).

Exercise

Problem. Translate the paper statement "if PP implies QQ and QQ implies RR, then PP implies RR" into a Lean type and provide a term-mode proof.

Hint. The proof is function composition.

Solution.

theorem transitivity (P Q R : Prop) (hPQ : P → Q) (hQR : Q → R) : P → R :=
  fun hP => hQR (hPQ hP)

Or, using function composition (·):

theorem transitivity (P Q R : Prop) (hPQ : P → Q) (hQR : Q → R) : P → R :=
  hQR ∘ hPQ

The kernel accepts either; the second is a pure-term version that mathematicians may find more natural. The fact that "if-then composition" and "function composition" are the same term is exactly the Curry-Howard correspondence applied here.

Implementation Note

When debugging a stuck proof, #check, #print, and the goal view in VS Code are essential. #check term shows the type. #print term shows the definition. The Lean LSP shows the current proof state at the cursor; reading the displayed goal as a type makes the next tactic choice obvious. If the goal is P → Q, you usually want intro h (introduce the hypothesis, leaving goal Q with new hypothesis h : P). If the goal is P ∧ Q, constructor splits into two subgoals. Each tactic is the term-construction operation read in tactic-mode syntax.

Connection to TheoremPath

Every theorem in the FormalSLT manifest corresponds, under Curry-Howard, to a kernel-checked term of the appropriate Lean type. The manifest lists, for each theorem, the file, the type signature, the term name, and the axioms the term depends on. The trust story is: the kernel saw the term, the kernel type-checked the term, the term has the declared type, therefore the proposition is provable. No other "proof check" happens.

Cross-references: /methodology explains why the kernel-check is the right notion of "verified", and /evidence lists the specific Mathlib commit and toolchain pin.