From paper to kernel: three lectures of type theory, now made executable and machine-checked.
dr Bartosz Naskręcki · Adam Mickiewicz University in Poznań · CCAI, Warsaw Univ. of Technology
▷ live lab: /lab-lambda
Why this matters
Lecture 1 gave us the judgment $\Gamma \vdash t : A$. Lecture 2 computed with pure $\lambda$-terms. Lecture 3 read the connectives as type formers.
Lean 4 is that story made executable — a dependently-typed $\lambda$-calculus where:
a proposition is a type, and
a proof is a term inhabiting it.
Nothing new conceptually — but now a machine checks every step.
Why trust a machine?
A deliberate architectural choice: only a small type-checking kernel must be correct.
Whatever produced a proof, it is ultimately a term; the kernel verifies that term has the claimed type. Elaborator, tactics, pretty-printer — tens of thousands of lines — may all have bugs without endangering soundness.
This is the de Bruijn criterion: "the computer says the proof is correct" means something precise.
The second reason: Mathlib
One coherent library, on the order of 283,000 theorems and 135,000 definitions (2026).
Formalizing modern mathematics is feasible because you rarely start from nothing — you cite a lemma. Searching for the right name is itself a skill.
Today: two modes & the kernel · Prop vs data · induction · rewriting · a first honest end-to-end proof · automation & tooling.
Two modes, one term
Every proof is a term — you can write it two ways. Term mode: write the inhabitant directly. Tactic mode: a by block whose tactics build the term.
theorem modus_ponens {P Q : Prop} : (P → Q) → P → Q :=
fun f p => f p -- term mode: modus ponens IS application
theorem modus_ponens' {P Q : Prop} : (P → Q) → P → Q := by
intro f p
exact f p -- tactic mode: same kernel term
#print modus_ponens' reveals fun f p => f p. They differ only in how much of the term you write.
Symmetry of ∧, both ways
Conjunction is a structure with two fields — a proof of $P \land Q$ is a pair, and swapping is trivial.
-- term mode: destructure the pair, rebuild it swapped
theorem and_comm_term {P Q : Prop} : P ∧ Q → Q ∧ P :=
fun ⟨hp, hq⟩ => ⟨hq, hp⟩
-- tactic mode: the SAME term, built step by step
theorem and_comm_tac {P Q : Prop} (h : P ∧ Q) : Q ∧ P := by
obtain ⟨hp, hq⟩ := h -- split into hp : P, hq : Q
constructor -- goal Q ∧ P → two goals: Q, then P
· exact hq
· exact hp
Propositions vs data
A P : Prop is proof-irrelevant: any two proofs of P are equal. Data n : Nat is not — 2 and 3 differ. So you may not pattern-match a proof to extract data (large elimination).
The Curry–Howard dictionary as concrete datatypes:
$P \Rightarrow Q$ — a function P → Q
$P \land Q$ — a pair / structure And P Q
$P \lor Q$ — a sum Or P Q
$\exists x,\ p\,x$ — a dependent pairExists p
$\forall x,\ p\,x$ — a dependent function (x : α) → p x
▷ live lab: /lab-lambda — try ch lib
this dictionary, live: each entry as a λ-term, its principal type, and its Lean spelling
An existential is a pair
The punchline students remember: $\exists$ is not a mystical quantifier — its constructor is $\langle \text{witness},\ \text{proof} \rangle$.
-- the proof IS the pair (witness 2, a proof that 2^2 = 4)
theorem exists_sq_four : ∃ n : Nat, n ^ 2 = 4 := ⟨2, rfl⟩
rfl works because $2^2$ computes to $4$: both sides share a normal form, so they are definitionally equal. Likewise 2 ∣ n is by definition∃ c, n = 2 * c — the same proposition.
equiv POW 2 2 = 4
The naturals and induction
Nat is an inductive type — Peano's axioms as constructors. Addition recurses on its second argument:
$$ m + 0 = m, \qquad m + (n+1) = (m + n) + 1. $$
This asymmetry is the surprise of the day: $m + 0 = m$ is a defining equation (holds by rfl), but $0 + n = n$ is not — the recursion never inspects a left zero, so it must be proved by induction.
▷ peano 3
SUCC (SUCC (SUCC ZERO)) next to λf x. f (f (f x)): Lecture 2's constructors are Lean's Nat.zero/Nat.succ
Commutativity, in four lines
On a private copy of the naturals, rebuild it from the two defining equations (as in NNG4):
theorem zero_add (n : N) : add .zero n = n := by
induction n with
| zero => rfl
| succ k ih => rw [add_succ, ih]
theorem add_comm (m n : N) : add m n = add n m := by
induction n with
| zero => rw [add_zero, zero_add]
| succ k ih => rw [add_succ, succ_add, ih]
The successor case assumesih : add m k = add k m and rewrites the goal to a syntactic identity. Exactly NNG4's Addition World star.
Rewriting: rfl, rw, simp
rfl — closes a = a when both sides are definitionally equal. Proves $m+0=m$ and $2^2=4$; not $0+n=n$.
rw [h] — rewrites the goal left-to-right by h. Fails if the LHS is not present syntactically. Use rw [← h] for the reverse; chain rw [h₁, h₂].
simp — repeatedly rewrites with the whole @[simp] set until nothing applies. Powerful but opaque — prefer targeted rw for a known step.
A first honest end-to-end proof
Gauss's sum, kernel-checked: $$ 2\sum_{k=0}^{n} k = n(n+1). $$
import Mathlib
theorem gauss (n : Nat) :
2 * (∑ k ∈ Finset.range (n + 1), k) = n * (n + 1) := by
induction n with
| zero => simp
| succ n ih => rw [Finset.sum_range_succ, Nat.mul_add, ih]; ring
The step peels off the last summand, distributes, substitutes ih, and lets ring clear the polynomial dust — a complete sorry-free proof.
…and cite the library
The other mental model: the right move is usually to find the lemma. Euclid's theorem is already Nat.exists_infinite_primes.
theorem prime_above (N : Nat) : ∃ p, N < p ∧ p.Prime := by
obtain ⟨p, hN, hp⟩ := Nat.exists_infinite_primes (N + 1) -- gives N+1 ≤ p
exact ⟨p, by omega, hp⟩ -- omega: N+1 ≤ p ⟹ N < p
Find the name, destructure its output, let omega finish. Searching (exact?, Loogle, LeanSearch) is a core skill, not cheating.
Automation: match the shape
The cure for love/burn with automation is knowing each tactic's scope.
decide — a decidable prop, by evaluating its instance. Fails if undecidable or too large.
omega — linear integer/nat arithmetic (Presburger). Will not touch any product of variables $x \cdot y$.
ring — identities in a commutative (semi)ring. Not inequalities.
linarith / nlinarith — inequalities from hypotheses; needs the right hints.
"Just try omega" on a nonlinear goal is a real time sink.
Run it yourself
Set up a real Lean 4 + Mathlib project — elan pins the compiler, lake exe cache get downloads prebuilt Mathlib (never skip it), and #print axioms gauss audits for a hidden sorry.
Open the Lambda Lab and take the guided tour:
tour
Then lean add_comm for the offline Addition-World trace, and kb mathlib for the lemma-search cheatsheet. Every proof here is reproducible in the browser.
Recap & references
Term = tactic: a by block builds a $\lambda$-term the kernel checks (de Bruijn criterion).
Prop vs data: proof-irrelevance; $\exists$ is a pair.
Induction & rewriting: $m+0=m$ by rfl, $0+n=n$ by induction; rfl/rw/simp.
Build one, cite the rest: Gauss by hand, Euclid from Mathlib; match automation to the goal.
Theorem Proving in Lean 4 · Mathematics in Lean · Natural Number Game 4 · Macbeth, Mechanics of Proof · lean-lang.org/install