Lean in Practice: A Proof from the Inside

A Theorem You Already Know How to Prove From Moves to Tactics The Translation in Full The Verification Loop

A Theorem You Already Know How to Prove

The preceding page treated formal methods from the outside — their history, their architecture, the structural reorganization of trust that proof assistants make possible. This page does the opposite. We pick a single theorem that every reader of the analysis section has already met, and we walk through its proof first in ordinary mathematical prose, then as a Lean script. The goal is to make the translation visible: not to teach Lean as a foreign language, but to show that the moves of tactic-mode proof are names for moves that any working mathematician already performs.

Surface syntax in this page reflects Lean 4 and mathlib as of May 2026. The translation between strategic moves and tactic invocations is structural; the specific keywords, tactic names, and mathlib lemma names that appear below may evolve while that structural correspondence remains.

The theorem is the triangle inequality for real numbers,

\[ |x + y| \leq |x| + |y|, \qquad x, y \in \mathbb{R}. \]

A reader who has worked through the real-analysis material has seen this several times. What matters here is not the result but the shape of the argument — the strategic decomposition a mathematician produces when asked to prove it. We will write that argument once, in full, with no reference to Lean. The same argument will reappear in the next section, annotated; and the section after that will exhibit it as Lean source.

The strategic decomposition

A useful characterization of the absolute value is the equivalence

\[ |a| \leq b \iff -b \leq a \text{ and } a \leq b, \]

valid for any real numbers \(a, b\) with \(b \geq 0\). Applied to the left-hand side of the triangle inequality with \(a = x + y\) and \(b = |x| + |y|\) — which is non-negative since absolute values are — the single inequality \(|x + y| \leq |x| + |y|\) becomes a pair of inequalities:

\[ -(|x| + |y|) \leq x + y \qquad \text{and} \qquad x + y \leq |x| + |y|. \]

This is the first strategic move: use the characterization to split the goal into two parts. We now have two inequalities to prove, each simpler than the original because each is one-sided.

The two pieces

Consider the upper inequality \(x + y \leq |x| + |y|\). Two facts about the absolute value are immediate:

\[ x \leq |x| \qquad \text{and} \qquad y \leq |y|. \]

These hold for every real number — they are part of what absolute value means. Adding them gives \(x + y \leq |x| + |y|\), which is exactly the inequality we wanted. The argument for this piece is therefore: introduce two known facts, then perform a routine arithmetic step.

The lower inequality \(-(|x| + |y|) \leq x + y\) is symmetric. The corresponding facts are

\[ -|x| \leq x \qquad \text{and} \qquad -|y| \leq y, \]

which again hold for every real number. Adding them yields \(-(|x| + |y|) = -|x| + (-|y|) \leq x + y\), which is the inequality we wanted. Same shape of argument: introduce two known facts, perform a routine arithmetic step.

The complete argument, condensed

Reading back over what we just did, the proof has four strategic moves and nothing else:

  1. Use the characterization \(|a| \leq b \iff -b \leq a \text{ and } a \leq b\) to replace the goal with a conjunction of two one-sided inequalities.
  2. Decompose the conjunction into two independent goals — prove one, then prove the other.
  3. For each goal, introduce the two relevant facts about absolute value (\(x \leq |x|\) and \(y \leq |y|\), or their negative analogues).
  4. Close each goal with a routine arithmetic step combining the two facts.

Every step of this argument is a move that the reader has performed dozens of times in other proofs. None of the moves requires creative insight; the proof is, in the working mathematician's vocabulary, routine. That routineness is exactly what we will exploit. The moves listed above have names — names a proof assistant uses to mechanize them. The next section gives those names.

From Moves to Tactics

Each of the four moves identified at the end of the previous section has a name in Lean. Before we look at the Lean source as a whole, we walk down the list and pair each move with its name. The names will look unfamiliar on first encounter, but the substance behind each one is something the reader has already done in the proof above.

The four moves and their names

Move 1: Use a characterization to rewrite the goal. The mathematician's instinct, faced with \(|x + y| \leq |x| + |y|\), is to recall that \(|a| \leq b\) is equivalent to a conjunction of two simpler inequalities, and to apply that equivalence to transform the goal. The Lean tactic that performs this substitution along a known equivalence is rw (short for "rewrite"). Concretely, mathlib registers the equivalence \(|a| \leq b \iff -b \leq a \land a \leq b\) under the name abs_le, and the invocation rw [abs_le] replaces the goal \(|x + y| \leq |x| + |y|\) with the conjunction \(-(|x| + |y|) \leq x + y \land x + y \leq |x| + |y|\).

Move 2: Decompose a conjunction into its two parts. Having a single goal of the form \(P \land Q\), the mathematician separates the two pieces and addresses them independently. The Lean tactic for this is constructor. After constructor, what was a single goal \(P \land Q\) becomes two subgoals — \(P\) and \(Q\) — each addressed on its own.

The connection back to formal methods is direct. A proof of \(P \land Q\) is, under the correspondence between propositions and types, a pair whose components are proofs of \(P\) and \(Q\). The constructor tactic is the instruction to build that pair by constructing each component separately.

Move 3: Introduce a known fact as a named premise. When the mathematician writes "we know that \(x \leq |x|\)" and proceeds to use this fact, the act is one of bringing an external lemma into the local context and giving it a name. The Lean tactic for this is have. The syntax have h : x ≤ |x| := le_abs_self x reads almost as English: "let \(h\) be the fact that \(x \leq |x|\), which holds by the mathlib lemma le_abs_self applied to \(x\)." After this line, \(h\) is available as a hypothesis for the rest of the current goal.

The mathlib name le_abs_self is not a code one needs to memorize. It is built from English fragments according to a regular convention: a statement of the form \(a \leq \text{something}(a)\) reads as a is less-than-or-equal to something of a, and mathlib's lemma names are written in that order. Most lemmas one wants to use in a Lean proof can be guessed or discovered interactively, a point we return to in the final section.

Move 4: Close the goal by routine arithmetic. The final step in each branch of the proof is to combine two facts of the form \(a \leq b\) by addition. This is the kind of arithmetic that a mathematician performs without comment, and it is also the kind of step that Lean is willing to perform without comment. The tactic linarith (short for "linear arithmetic") closes any goal that follows from the linear-arithmetic combination of available hypotheses. Given the hypotheses \(-|x| \leq x\) and \(-|y| \leq y\), the goal \(-(|x| + |y|) \leq x + y\) follows linearly, and linarith dispatches it.

Tactics as named moves, not foreign vocabulary

The four tactics just introduced — rw, constructor, have, linarith — are not language features that one learns the way one learns the syntax of a new programming language. Each is the name of a move that already has an intuitive content for the reader:

Each entry of this list paraphrases the corresponding move from the strategic decomposition. The Lean tactic does mechanically what the mathematician's prose does in words. Tactic mode, in this sense, is not a separate skill from proof construction; it is a notation for proof construction at the strategic level, with the bookkeeping handled by the system.

The shape of a Lean theorem

Before we write the full Lean proof, three pieces of syntax need to be visible. A theorem in Lean has the form

theorem name (arg₁ : T₁) (arg₂ : T₂) : statement := proof

The pieces are: the keyword theorem, a name chosen by the author, parenthesized arguments with their types, a colon separating the arguments from the statement being proved, and the symbol := separating the statement from its proof. The proof itself can be a single term written directly (as we saw on the preceding page) or — when the proof is constructed by tactics — the keyword by followed by an indented sequence of tactic invocations.

When a tactic-mode proof has multiple subgoals open (as it will after constructor), the convention is to focus on each subgoal in turn using the bullet symbol ·. Each bulleted block addresses one subgoal; once that subgoal is closed, the next bullet picks up the next subgoal. This is the only syntactic detail of tactic-mode proofs that the next section will require.

With these pieces in place, the four moves translate directly into a Lean script. The next section exhibits the script and walks through what happens at each step.

The Translation in Full

Here is the complete Lean proof. After the script, we walk through it line by line, showing the proof state at the points where the state changes shape.


                    theorem triangle_inequality (x y : ℝ) : |x + y| ≤ |x| + |y| := by
                    rw [abs_le]
                    constructor
                    · have h1 : -|x| ≤ x := neg_abs_le x
                        have h2 : -|y| ≤ y := neg_abs_le y
                        linarith
                    · have h1 : x ≤ |x| := le_abs_self x
                        have h2 : y ≤ |y| := le_abs_self y
                        linarith
                

Each tactic invocation in this script corresponds to one move in the strategic decomposition of the previous section. The rewriting along the characterization, the splitting of the conjunction, the introduction of named facts, the closing arithmetic — each appears once per branch, in the order the mathematician would perform it. The correspondence between strategic moves and tactic invocations is one-to-one. That correspondence is not an accidental feature of this particular proof; it is the design intent of tactic mode itself.

The state at the start

Before any tactic has been applied, Lean shows the proof state as

x y : ℝ
⊢ |x + y| ≤ |x| + |y|

The hypotheses appear above the horizontal turnstile , the goal below. At this moment the local context contains the two real variables x and y, and the goal is the inequality we set out to prove.

After rw [abs_le]

The tactic rw [abs_le] rewrites the goal along the equivalence \(|a| \leq b \iff -b \leq a \land a \leq b\). After this tactic, the proof state becomes

x y : ℝ
⊢ -(|x| + |y|) ≤ x + y ∧ x + y ≤ |x| + |y|

The goal is now a conjunction. Both sides remain to be proved, and we have committed to no particular strategy beyond the application of the characterization. The substantive content of this step is the move from "prove an absolute-value inequality" to "prove two simpler one-sided inequalities."

A small notational remark is worth making here. The mathematician's prose in the opening section stated the characterization \(|a| \leq b \iff -b \leq a \land a \leq b\) under the hypothesis \(b \geq 0\), as is customary in analysis textbooks. The mathlib lemma abs_le drops this hypothesis: it asserts the iff for all \(a, b\) without qualification. The two statements are mathematically compatible — when \(b < 0\), the left-hand side \(|a| \leq b\) is false (because absolute values are non-negative) and the right-hand side \(-b \leq a \land a \leq b\) is also false (because it would require \(-b \leq a \leq b\) with \(-b > 0 > b\)), so the iff holds vacuously. Library design in mathlib favors the unconditional form because it requires no hypothesis to invoke. The mathematician's prose and the library's formulation describe the same fact, registered for different purposes.

After constructor

The tactic constructor takes the conjunction goal and produces two subgoals, one for each conjunct. The first subgoal — labelled case left — becomes the active proof state:

case left
x y : ℝ
⊢ -(|x| + |y|) ≤ x + y

A second subgoal labelled case right remains pending behind it; once the left subgoal is closed, that second subgoal will become active in turn. The bullet · on the next line of the script focuses on the first subgoal. Once that subgoal is closed, the bullet on a later line picks up the second subgoal. This is the structural counterpart of the mathematician's "first we prove this, then we prove that" — Lean asks the author to handle the two parts in visibly separate blocks.

After the two have lines in the first branch

Inside the first bullet, the lines

  have h1 : -|x| ≤ x := neg_abs_le x
  have h2 : -|y| ≤ y := neg_abs_le y

introduce the two facts about the absolute value that the first branch requires. The mathlib lemma neg_abs_le applied to x produces a proof of \(-|x| \leq x\), which is bound to the name h1; similarly for h2. After these two lines the proof state is


                    case left
                    x y : ℝ
                    h1 : -|x| ≤ x
                    h2 : -|y| ≤ y
                    ⊢ -(|x| + |y|) ≤ x + y
                

The local context has gained two named hypotheses. The goal is unchanged. We are now in the position the mathematician was in halfway through the first branch of the prose argument: two known facts at hand, one combination away from the conclusion.

The closing linarith

The tactic linarith searches for a proof of the current goal by linear arithmetic over the hypotheses available in the local context. Given h1 and h2 as above, the inequality \(-(|x| + |y|) \leq x + y\) follows by adding h1 and h2 and rearranging — a manipulation entirely within the scope of linear arithmetic. The tactic finds the proof and closes the goal.

The second branch (handling the case \(x + y \leq |x| + |y|\)) proceeds identically with the symmetric lemmas. After the closing linarith of that branch, no goals remain and the proof is complete.

Partial proofs and the role of sorry

One feature of Lean that is worth seeing even in a first encounter is the keyword sorry. A reader constructing the proof above for the first time might want to establish the overall structure before filling in the details — to see whether the rewriting, the case split, and the introduction of named hypotheses are correctly arranged, and only afterward worry about whether linarith actually closes each goal. Replacing the final tactic in each branch with sorry produces a script that Lean accepts as a proof outline:


                    theorem triangle_inequality (x y : ℝ) : |x + y| ≤ |x| + |y| := by
                    rw [abs_le]
                    constructor
                    · have h1 : -|x| ≤ x := neg_abs_le x
                        have h2 : -|y| ≤ y := neg_abs_le y
                        sorry
                    · have h1 : x ≤ |x| := le_abs_self x
                        have h2 : y ≤ |y| := le_abs_self y
                        sorry
                

Lean reports two warnings — one for each sorry — but otherwise accepts the script. The author has demonstrated, to the system and to themself, that the strategic skeleton is sound. The arithmetic steps remain to be filled in, but their place in the proof is fixed. This top-down style of proof construction, with sorry as a placeholder for "the rest will follow," is one of the principal ways that Lean proofs are written in practice.

What happens when something goes wrong

Suppose the author types le_abs_self where neg_abs_le was meant. The line

  have h1 : -|x| ≤ x := le_abs_self x

asks Lean to bind h1 to a proof of \(-|x| \leq x\), but offers a term whose type is \(x \leq |x|\). The types do not match, and Lean reports the mismatch. The error is not a punishment for making a mistake; it is information about where the script's stated intent and its actual content have come apart. The author either repairs the lemma name or repairs the stated type — whichever was wrong — and the script proceeds.

This back-and-forth is the working rhythm of tactic-mode proof construction. The proof is rarely written from top to bottom in a single pass. The author writes, the system reports states and errors, the author adjusts. What looks on the page like a static sequence of tactics is the residue of a dialogue.

The Verification Loop

The proof on the page exists inside an ecosystem. Every named lemma that appeared in the previous section — abs_le, le_abs_self, neg_abs_le — was an entry in mathlib, the community-maintained library of theorems formalized in Lean. This final section describes the role of that library and the working practices that surround it, and then returns to the question of what we have and have not done by writing the proof.

mathlib as ambient context

A Lean file written in isolation has no theorems available to it. A file that begins with import Mathlib inherits the entire library — every theorem of undergraduate real analysis, linear algebra, group theory, point-set topology, and a substantial portion of graduate-level material besides. The triangle-inequality proof in the previous section rested on mathlib at every step: the equivalence abs_le, the lemmas le_abs_self and neg_abs_le, the decision procedure inside linarith, and the definition of the absolute value itself.

The practical consequence is that authoring a proof in Lean is, much of the time, an exercise in finding the right name. A mathematician proving the triangle inequality with pen and paper does not need to know that le_abs_self is the name of the fact \(x \leq |x|\); they need only to know the fact itself. A mathematician proving the same theorem in Lean needs to know — or to find — the name under which the fact is registered.

Searching the library

Lean provides tactics for searching mathlib from inside a proof in progress. The tactic exact?, placed where a closing term would go, asks Lean to search for a library lemma that closes the current goal directly. The tactic apply? searches for a lemma whose conclusion matches the goal up to the introduction of new subgoals. External tools such as Loogle accept a description of a lemma's shape — its pattern of quantifiers, hypotheses, and conclusion — and return matching candidates from mathlib's index.

A working session in Lean therefore alternates between two activities: writing tactics that encode strategic moves, and querying the library for the specific lemmas those moves require. The author moves between a local view of the proof under construction and a global view of what facts mathlib has already established. The library is large enough that most of the lemmas an undergraduate analysis course will use are already present; it is also large enough that knowing how to search it is itself a substantial skill, of a piece with the skill of constructing the proof.

The same theorem, in one line

A search of mathlib for the triangle inequality returns it as the lemma abs_add_le, whose statement is exactly \(\forall a\, b : \mathbb{R},\ |a + b| \leq |a| + |b|\). With this lemma in hand, the entire proof reduces to one line:

theorem triangle_inequality (x y : ℝ) : |x + y| ≤ |x| + |y| := abs_add_le x y

The proof term is abs_add_le x y — the library lemma applied to the two specific real numbers. No tactic mode, no have, no linarith. A mathlib library of over two hundred thousand theorems would not omit a result this basic; the one-line proof is the realistic Lean style for any theorem already in the library.

The reconstruction in the previous section was therefore not the proof that a working Lean user would write in practice. It was a reconstruction as if the relevant lemmas — but not the theorem itself — were available. That reconstruction had a pedagogical purpose: to exhibit the translation between strategic moves and tactic invocations on a theorem familiar enough that the strategic moves themselves required no explanation. A practitioner who encounters the same theorem in their own work looks first to mathlib; only when the relevant result is absent does the longer reconstruction become the actual workflow. Most of working with Lean is the alternation between these two regimes — finding what is already there, and proving what is not.

What the kernel verified

Once the proof was written and accepted, the verifying agent was not the tactic framework, not the linarith decision procedure, not mathlib. Tactics build candidate proof terms; the kernel inspects those terms. A buggy tactic that produces an ill-typed term, or a tactic that gestures at a proof without actually constructing the required term, cannot bypass the kernel — the kernel re-checks each term against the proposition with no reference to how the term was produced. This is the structure that the preceding page laid out under the name of the de Bruijn criterion. The triangle-inequality proof is an instance of it.

The instance is worth pausing over. The script in the previous section was authored by a person working out a strategy in informal mathematical prose, then translating that strategy into named tactic invocations. Nothing in that authoring process was verified by Lean. The verification began only at the moment the script was submitted to the kernel. The same script, had it been authored by some other agent — by a different mathematician, by a machine, by an automated proof-search routine — would have been verified by exactly the same procedure to exactly the same standard. The kernel reads the term; the kernel accepts or rejects.

This is the operative content of the distinction, drawn on the preceding page, between proof verification and proof discovery. Discovery is what the human did in the opening section, in informal prose, before any Lean tactic was invoked. Verification is what the kernel did, after the script was complete, by inspecting the proof term that the tactics produced. The two activities are separable, and Lean's architecture insists on the separation. A formal proof, in the modern sense, is not a proof that a human has been relieved of constructing; it is a proof whose construction is open to any agent capable of producing a verifiable term, while the standard of correctness remains fixed at the kernel.

What remains, and what does not

What the kernel verified was the implication: given the propositions registered in mathlib as abs_le, le_abs_self, and neg_abs_le, and given the soundness of linarith's linear-arithmetic procedure, the assertion \(|x + y| \leq |x| + |y|\) holds for all real \(x, y\). The kernel did not verify that the mathlib lemmas are themselves correctly stated, nor that they capture what the author of those lemmas intended. Those verifications happened earlier — when each lemma was added to mathlib and reviewed by the library's contributors — and they too rest, at bottom, on the same kernel. The trust chain bottoms out where the preceding page said it bottoms out: in the kernel implementation, and in the human review of specifications.

A reader who has followed the construction from the strategic decomposition through the tactic translation to the kernel-verified term has now seen, in one example, what a proof-assistant proof actually is. It is not a proof that the system found; it is a proof that the system checked. The thinking is human; the bookkeeping is mechanical; the verification is structural. Subsequent encounters with Lean in the curriculum — and there will be encounters, as the mathematical and computer-science material in later sections grows in technical weight — will take this shape as given. The triangle inequality is the smallest example that exhibits all the moving parts.

What the Kernel Actually Sees

The nine-line reconstruction in this page is a proof at human resolution. Each tactic invocation in it stands for a strategic move that the working mathematician would name in prose. The kernel, however, does not check tactics; the kernel checks the proof term that the tactics produce, and that term is written at a much finer resolution. To see what that means concretely, we set aside the triangle inequality and look at an identity that every reader has known since middle school:

\[ (a + b)^2 = a^2 + 2ab + b^2. \]

A student writes this in one line. Asked to justify each step — to identify precisely which property of the ring \(\mathbb{R}\) is being invoked at each move, and in what order — the same student begins to discover that the one-line identity contains many implicit steps. Below is the identity expanded into the level of detail at which the kernel works. The expansion has eighteen steps. None of them is wrong; none of them is optional. Each is a single invocation of a ring axiom, a definition, or the congruence rule that allows substitution of equals into a surrounding expression. Where a lemma name from mathlib applies, it is given in parentheses.

Phase 1 — Expanding the square.

  1. Definition of the square (pow_two): \(\ (a + b)^2 = (a + b)(a + b).\)
  2. Left distributivity (mul_add) — treat \((a + b)\) as a single block and distribute it over the right factor: \(\ (a + b)(a + b) = (a + b)a + (a + b)b.\)
  3. Right distributivity (add_mul) — expand the first half: \(\ (a + b)a = a \cdot a + b \cdot a.\)
  4. Right distributivity again (add_mul) — expand the second half: \(\ (a + b)b = a \cdot b + b \cdot b.\)
  5. Substitution by the congruence rule (congr) — combining steps 3 and 4 into the result of step 2: \(\ (a + b)(a + b) = (a \cdot a + b \cdot a) + (a \cdot b + b \cdot b).\)

Phase 2 — Normalizing the terms.

  1. Reverse application of the definition of the square (← pow_two): \(\ a \cdot a = a^2\) and \(b \cdot b = b^2.\)
  2. Commutativity of multiplication (mul_comm): \(\ b \cdot a = a \cdot b.\)
  3. Substitution by the congruence rule (congr) — applying steps 6 and 7 inside the expression from step 5: \(\ (a \cdot a + b \cdot a) + (a \cdot b + b \cdot b) = (a^2 + a \cdot b) + (a \cdot b + b^2).\)

Phase 3 — Regrouping by associativity.

The current expression has shape \((A + B) + (C + D)\); the target has shape \((A + (B + C)) + D\) after the middle terms have been combined. We need to move the two copies of \(a \cdot b\) next to each other, which is a matter of reassociating the additions.

  1. Associativity of addition (add_assoc) — shift the outer grouping to the right: \(\ (a^2 + a \cdot b) + (a \cdot b + b^2) = a^2 + (a \cdot b + (a \cdot b + b^2)).\)
  2. Reverse associativity of addition (← add_assoc) — regroup the inner sum: \(\ a \cdot b + (a \cdot b + b^2) = (a \cdot b + a \cdot b) + b^2.\)
  3. Substitution by the congruence rule (congr) — applying step 10 inside the expression from step 9: \(\ a^2 + (a \cdot b + (a \cdot b + b^2)) = a^2 + ((a \cdot b + a \cdot b) + b^2).\)

Phase 4 — Recognizing the coefficient 2.

Now \((a \cdot b + a \cdot b)\) is the term that intuition calls "\(2ab\)." Lean does not see this as a single fact; the kernel sees only the multiplicative unit and the distributive law. To get to \(2 \cdot (a \cdot b)\) the term has to be rebuilt from \(1 \cdot (a \cdot b)\) via reverse distributivity, and the numeral \(2\) has to be recognized as \(1 + 1\).

  1. Reverse application of the left multiplicative identity (← one_mul): \(\ a \cdot b = 1 \cdot (a \cdot b).\)
  2. Substitution by the congruence rule (congr) — applied to both terms: \(\ a \cdot b + a \cdot b = 1 \cdot (a \cdot b) + 1 \cdot (a \cdot b).\)
  3. Reverse right distributivity (← add_mul) — factor out \((a \cdot b)\): \(\ 1 \cdot (a \cdot b) + 1 \cdot (a \cdot b) = (1 + 1) \cdot (a \cdot b).\)
  4. Definition of the numeral 2 (one_add_one_eq_two): \(\ 1 + 1 = 2.\)
  5. Substitution by the congruence rule (congr) — applying step 15 inside the expression from step 14: \(\ (1 + 1) \cdot (a \cdot b) = 2 \cdot (a \cdot b).\) Henceforth we write \(2 \cdot (a \cdot b)\) as \(2ab.\)

Phase 5 — Final assembly.

  1. Substitution by the congruence rule (congr) — substitute the result of Phase 4 into the expression from step 11: \(\ a^2 + ((a \cdot b + a \cdot b) + b^2) = a^2 + (2ab + b^2).\)
  2. Reverse associativity of addition (← add_assoc) — restore the standard left-associated polynomial form: \(\ a^2 + (2ab + b^2) = (a^2 + 2ab) + b^2.\)

Eighteen steps. Every one is a primitive application of a ring axiom or a definition; every substitution that the eye performs automatically corresponds to an explicit invocation of the congruence rule. The identity itself takes a single line to write, and mathlib does in fact register it as a one-line lemma (add_sq) so that a working Lean user can invoke it directly. The expansion above is not the literal proof term that add_sq's mathlib implementation produces — that term is built by an automated tactic and may differ from the human-friendly ordering used here — but it is faithful to the level of resolution at which the kernel checks the proof. Every primitive step the kernel sees is of one of the kinds enumerated above.

Three observations follow. First, the proof at human resolution and the proof at kernel resolution are not different proofs of the same identity; the strategic-level moves a working mathematician names in prose compress the primitive-level steps that the kernel actually checks, and the compressions are reliable only because the underlying steps are mechanically checked. Second, every step a human treats as "obvious" — every substitution that the eye carries out without comment — corresponds to a discrete rule in the kernel's rulebook. The kernel does not have a category of "obvious"; it has only application of axioms and definitions. Third, the skill of recognizing that \((a + b)^2 = a^2 + 2ab + b^2\) is universal among educated adults; the skill of exhaustively justifying it is the mathematical maturity that years of rigorous training are meant to build. Formal verification asks for the latter, and is reliable in proportion to its insistence on it.

The nine-line reconstruction earlier in this page omits this layer of resolution because no human reader could profitably inspect it. The kernel can. That asymmetry — a human checks at the strategic level, a kernel checks at the primitive level, and the translation between them is mechanical — is what the preceding page meant by the de Bruijn criterion. The triangle inequality saw the kernel at work; this expansion shows what the kernel was actually looking at.