VIASM Mini-Course · Hanoi 2026 · Lecture 4

Introduction to Lean

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 pair Exists 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 assumes ih : 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.
  • omegalinear 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