Induction on Derivations

We now have relations defined by inference rules — evaluation e \Downarrow v, typing \vdash e : \tau, and so on. The obvious next question is: how do we prove something true of every derivable judgement, when there are infinitely many of them? We cannot check them one by one. The answer is the single most important proof technique in the whole field: induction — and specifically rule induction (induction on derivations), the tool that turns "the relation is the least set closed under the rules" into a licence to prove universal statements.

You already know its baby brother. Ordinary mathematical induction proves P(n) for all naturals by checking P(0) and "P(n) \Rightarrow P(n+1)". Structural induction generalises this from numbers to inductively-defined data (our expression trees); rule induction generalises it once more, to derivations built from inference rules. All three are the same idea — cover a base case, then an inductive step for each way of building something bigger — differing only in what you are inducting over.

Structural induction on syntax

Recall our expressions are the least set with numerals n as base cases and e_1 + e_2, e_1 \times e_2 as inductive cases. Structural induction proves a property P(e) holds for all expressions by matching that shape:

Do all three and you have P(e) for every expression — because every expression is built by finitely many applications of exactly these constructors, and induction chases the proof down through its subtrees to the leaves. The pattern is one case per constructor, mirroring the datatype's variants: the same shape as the recursion that defines functions over the tree. Definition and proof march in lockstep.

Rule induction: induction on the derivation

Structural induction inducts on the expression. Sometimes we instead need to induct on the derivation of a judgement — the proof tree itself — because the property is about the derivation, or because the relation is not driven purely by one syntactic argument. This is rule induction, and it comes straight from the "least closed set" definition: to prove a property P holds of every derivable judgement, show that the set of judgements satisfying P is itself closed under the rules. Concretely:

Here is the inductive step for the (E-Add) rule, drawn out — the premises carry the induction hypothesis, and the conclusion is the goal:

Why is this valid? Because the relation is the least set closed under the rules. The judgements satisfying P form a set; the bullets above say exactly that this set is closed under every rule; and the least closed set is contained in any closed set. So every derivable judgement satisfies P. The induction hypothesis is legitimate for the same reason it is in ordinary induction: each premise J_i has a strictly smaller sub-derivation than the conclusion, so we are always appealing to P on something we have "already reached".

A full worked proof: evaluation is deterministic

Let us prove a real theorem end to end — the kind of result a semantics exists to make provable. For our arithmetic language with rules (E-Num), (E-Add), (E-Mul):

Proof. By rule induction on the derivation of e \Downarrow v (equivalently, structural induction on e). Take the property

P(e \Downarrow v) \;\equiv\; \text{for all } v',\ \text{if } e \Downarrow v' \text{ then } v = v'.

We consider each rule that could have concluded e \Downarrow v. Because the form of e determines which rule applies, this is a clean case split.

Case (E-Num). Here e = n and v = n. Suppose also n \Downarrow v'. The only rule whose conclusion can match a numeral n is (E-Num), which forces v' = n. Hence v = n = v'. (Base case — no induction hypothesis needed.)

Case (E-Add). Here e = e_1 + e_2, and the derivation ends in

\dfrac{e_1 \Downarrow v_1 \quad e_2 \Downarrow v_2}{e_1 + e_2 \Downarrow v_1 + v_2}\;\text{(E-Add)}, \qquad v = v_1 + v_2.

The induction hypotheses are: P(e_1 \Downarrow v_1) and P(e_2 \Downarrow v_2) — i.e. e_1 and e_2 each evaluate to at most one value. Now suppose e_1 + e_2 \Downarrow v'. Since e is a syntactic sum, the only rule that can conclude this is (E-Add) again, so its derivation must have the form e_1 \Downarrow v_1', e_2 \Downarrow v_2' with v' = v_1' + v_2'. By the induction hypothesis on e_1, v_1 = v_1'; by the induction hypothesis on e_2, v_2 = v_2'. Therefore v = v_1 + v_2 = v_1' + v_2' = v'.

Case (E-Mul). Identical to (E-Add), with \times for +: the only rule concluding e_1 \times e_2 \Downarrow v' is (E-Mul), the induction hypotheses give v_1 = v_1' and v_2 = v_2', and so v = v_1 \times v_2 = v_1' \times v_2' = v'.

All rules are covered, so P holds for every derivation, which is exactly the theorem. \blacksquare

Notice the two ingredients that did all the work, and that recur in every such proof: an inversion step ("the only rule that could conclude this judgement is…", which relies on the relation being the least closed set) and the induction hypothesis applied to the strictly smaller sub-derivations. Learn this shape and you can prove type soundness, confluence, and the rest.

The proof, checked by exhaustion in code

We cannot run an infinite proof, but we can illustrate its claim: build the evaluator faithfully from the rules, then check on many random expressions that a second, independent evaluation always agrees — the concrete shadow of "at most one value". Press Run:

type Exp = | { tag: "num"; n: number } | { tag: "add"; l: Exp; r: Exp } | { tag: "mul"; l: Exp; r: Exp }; // One clause per rule: (E-Num), (E-Add), (E-Mul). function evalExp(e: Exp): number { switch (e.tag) { case "num": return e.n; // E-Num case "add": return evalExp(e.l) + evalExp(e.r); // E-Add case "mul": return evalExp(e.l) * evalExp(e.r); // E-Mul } } // A tiny random-expression generator to stress-test determinism. function randomExp(depth: number): Exp { if (depth <= 0 || Math.random() < 0.35) return { tag: "num", n: Math.floor(Math.random() * 9) + 1 }; const l = randomExp(depth - 1), r = randomExp(depth - 1); return Math.random() < 0.5 ? { tag: "add", l, r } : { tag: "mul", l, r }; } // "Determinism": two independent evaluations of the SAME tree must agree, every time. let checked = 0, disagreements = 0; for (let i = 0; i < 10000; i++) { const e = randomExp(4); if (evalExp(e) !== evalExp(e)) disagreements++; // (can never happen — that IS the theorem) checked++; } console.log(`Checked ${checked} random expressions.`); console.log(`Disagreements: ${disagreements}`); // 0 — evaluation is a function console.log("As proved by rule induction: e ⇓ v fixes v uniquely.");

The count is always 0, of course — but that is the whole point of the theorem: the relation \Downarrow, which we defined with rules and could in principle have made ambiguous, is in fact a function. The proof told us so for all expressions at once; the code merely lets us watch it not fail.

For a syntax-directed relation like ours — where the form of e alone decides which rule applies — inducting on the expression and inducting on the derivation are interchangeable, and people say "structural induction on e" out of habit. The two genuinely part ways when a relation is not syntax-directed: think of a subtyping relation with a free-floating transitivity rule \frac{\tau_1 \le \tau_2\ \ \tau_2 \le \tau_3}{\tau_1 \le \tau_3}, whose conclusion mentions a type \tau_2 that does not appear in it, or a reflexive-transitive closure like multi-step reduction e \to^{*} e'. There is no single "smaller expression" to recurse on — but there is always a smaller sub-derivation. That is why the seasoned semanticist reaches for rule induction by default: it works uniformly, syntax-directed or not, because it is induction on the one thing that is always inductively built — the proof tree. Harper's PFPL makes exactly this its workhorse; Pierce's TAPL uses it for every soundness proof.

The most dangerous mistake in an induction proof is to strengthen the wrong quantifier — to state the induction hypothesis too weakly. In the determinism proof, notice P quantified over all v' ("for all v', if e \Downarrow v' then v = v'"). Had we fixed a single v' before the induction, the hypothesis handed to us in the (E-Add) case would have been about the wrong value and the proof would stall. Rule of thumb: put every universally-quantified variable that varies across the induction inside P, before you start. A slightly stronger, correctly-quantified statement is often easier to prove — the paradox of "generalising the induction hypothesis".

A second trap: applying the induction hypothesis to something that is not smaller. The IH is only licensed on the immediate sub-derivations (the premises of the last rule). Reaching for P on the whole judgement you are trying to prove, or on a sibling that is not a premise, is circular — it "proves" false things. Every legitimate appeal to the IH must point at a strictly smaller derivation, and if you cannot identify which one, you do not yet have a proof.