LeanChapter 01
Terms, types, propositions
Terms, types, propositions, functions, definitions, and the logical connectives used in Lean statements.
Contents
Statistical notation already distinguishes objects by kind. The expression
Every Lean expression has a type. A proposition is also a type, and a proof is a term of that type. To check a proof, Lean computes the term’s type and verifies that it matches the stated proposition.
This chapter concentrates on reading statements: identifying their objects, assumptions, and conclusions. The examples are executable; running them shows the types and values discussed in the text.
Everything has a type
Two commands do all the exploring. #check reports the type of an expression
without running it. #eval runs it.
#check (7 : ℕ)
#check (-3 : ℤ)
#check (2.5 : ℝ)
#check Real.sqrt
#eval (17 + 25 : ℕ)
#eval (10 : ℚ) / 4#eval refuses on ℝ, and it is worth understanding why now rather than being
surprised later. A real number in Lean is a Cauchy sequence up to equivalence:
an infinite object with no finite normal form. There is nothing to print. This
costs you nothing at all, because proofs do not evaluate — nobody ever needed
the decimal expansion of ℚ; when you want mathematics,
work in ℝ.
Types have types.
#check ℕ
#check ℝ
#check Prop
#check Type
#check ((2 : ℝ) + 2 = 4)
#check (rfl : (2 : ℕ) + 2 = 4)The distinction that matters is Prop against Type. ℝ : Type — real
numbers are data, and different real numbers are different. Prop is the type
of statements, and (2 : ℝ) + 2 = 4 is a term of type Prop: not a number,
not a Boolean, a claim.
The last #check in that block is the whole chapter in one line:
#check (rfl : (2 : ℕ) + 2 = 4)
We asked Lean for the type of the term rfl and told it to expect
2 + 2 = 4. It agreed. The proposition was used exactly as ℕ was used two
lines earlier — as a type — and the proof was used exactly as 7 was — as a
term of that type. This is the Curry–Howard correspondence, and after this
paragraph I will stop naming it and start using it.
Definitions and functions
A def names a term. Nothing more.
def totalOfThree (y : Fin 3 → ℚ) : ℚ := y 0 + y 1 + y 2
def outcomes : Fin 3 → ℚ := ![2, 5, 11]
#eval outcomes 1
#eval totalOfThree outcomes
#check totalOfThree
theorem totalOfThree_apply (y : Fin 3 → ℚ) : totalOfThree y = y 0 + y 1 + y 2 := rflNote the type Fin 3 → ℚ standing in for “an outcome vector of length three”.
Fin n is the type with exactly Fin n → ℝ as
Function application is juxtaposition: f x, not f(x). Parentheses in Lean
group, they do not apply, so f (x + 1) needs them and f x does not.
The last line of that block deserves a second look:
theorem totalOfThree_apply (y : Fin 3 → ℚ) : totalOfThree y = y 0 + y 1 + y 2 := rfl
rfl proves it because both sides reduce to the same term once the
definition is unfolded. This is how you check that a definition means what you
intended, and it is the cheapest sanity check available. It is also, as the
last section of this chapter shows, how an empty theorem gets written by
accident.
Every Lean function takes one argument.
def scale (c : ℚ) (y : Fin 3 → ℚ) : Fin 3 → ℚ := fun i => c * y i
#check scale
#check scale 2
#eval scale 2 ![1, 2, 3] 0
theorem scale_apply (y : Fin 3 → ℚ) : scale 2 y 2 = 2 * y 2 := rfl#check scale prints the signature with its binders named, but the type it
denotes is ℚ → (Fin 3 → ℚ) → (Fin 3 → ℚ), and → associates to the right, so
that is ℚ → ((Fin 3 → ℚ) → (Fin 3 → ℚ)): give it a scalar and you get back a
function waiting for a vector, which is what #check scale 2 reports. Partial
application therefore always works, and you will see it constantly in Mathlib,
where a lemma about f is applied to half its arguments and passed along as an
argument itself.
example, theorem, and what rfl is not
example : (2 : ℕ) + 2 = 4 := rfl
theorem two_plus_two : (2 : ℕ) + 2 = 4 := rfl
noncomputable def sampleMean (a b : ℝ) : ℝ := (a + b) / 2
theorem sampleMean_eq (a b : ℝ) : sampleMean a b = (a + b) / 2 := rflexample states and proves something without naming it: use it to check your
understanding. theorem gives it a name so later proofs can cite it. lemma
is a synonym — Lean does not distinguish them, and any hierarchy you read into
the two words is yours, not the compiler’s.
rfl is not a proof method, it is a term: the constructor of Eq saying “this
thing equals itself”. It closes a goal exactly when both sides reduce to the
same normal form. So it does closed arithmetic (2 + 2 and 4 both reduce to
4), and it unfolds definitions, and it does not do algebra: a + b = b + a
is not an instance of “both sides are the same term”, and needs add_comm.
Propositions are types, proofs are terms
Here is the payoff for taking the correspondence seriously: → is one symbol
doing two jobs, and they are the same job.
def constantOutcomes : Fin 3 → ℝ := fun _ => 4
theorem effect_nonneg_at_zero (y1 y0 : Fin 3 → ℝ) :
(∀ i, y0 i ≤ y1 i) → y0 0 ≤ y1 0 :=
fun h => h 0As a type former, Fin 3 → ℝ is the type of outcome vectors. As a
proposition former, p → q is implication. And the proof of an implication
is written fun h => …, with the same keyword as an anonymous function,
because it is an anonymous function: it takes a proof of the hypothesis and
returns a proof of the conclusion. In effect_nonneg_at_zero the proof is
fun h => h 0, which reads: given the assumption h, apply it at unit 0.
Applying a hypothesis and applying a function are the same operation.
This unification is why the table below is short. Each connective has one way in and one way out; in every case the way in is “build a term” and the way out is “take the term apart”.
| Connective | Introduced by | Eliminated by |
|---|---|---|
p → q | fun hp => … | application: h hp |
∀ i, P i | fun i => … | application: h i |
∃ i, P i | ⟨witness, proof⟩ | pattern match: fun ⟨i, hi⟩ => … |
p ∧ q | ⟨hp, hq⟩ | projections h.1, h.2 |
p ∨ q | Or.inl hp / Or.inr hq | Or.elim h f g — handle both cases |
¬ p | fun hp => … (it is p → False) | apply it: h hp : False |
p ↔ q | ⟨mp, mpr⟩ | h.mp, h.mpr |
The rest of this section is that table with one worked example per row.
∀
theorem one_scale (y : Fin 3 → ℝ) : ∀ i, 1 * y i = y i :=
fun i => one_mul (y i)
theorem one_scale_at_two (y : Fin 3 → ℝ) : 1 * y 2 = y 2 :=
one_scale y 2∀ i, P i is a dependent function type: a function from i to a proof of
P i, where the type of the output depends on the input. That is the only
difference between → and ∀, and it is why fun i => one_mul (y i) is a
proof of ∀ i, 1 * y i = y i. To use a ∀, apply it, as one_scale y 2 does.
If you take one thing from this chapter, take this: in a statement, ∀
costs nothing and means everything. theorem HT_unbiased (D : Design n) …
quantifies over every design in the universe. Chapter 8 makes that claim; the
reason it is worth making is that the ∀ is genuinely unconstrained.
∃
theorem exists_treated : ∃ i : Fin 3, (![2, 5, 11] : Fin 3 → ℚ) i = 5 :=
⟨1, rfl⟩
theorem exists_pos_of_exists_eq_three (y : Fin 3 → ℝ) :
(∃ i, y i = 3) → ∃ i, 0 < y i :=
fun ⟨i, hi⟩ => ⟨i, by rw [hi]; norm_num⟩The anonymous constructor ⟨_, _⟩ builds a term of any structure type, and
∃ i, P i is a structure with two fields: the witness and the proof. You must
produce the unit. Lean will not accept “such a unit must exist” as an argument
unless you have a term that says why. In exists_treated the witness is the
unit 1 and the proof is rfl, which suffices because ![2, 5, 11] 1
evaluates to 5: once the witness is fixed there is nothing left but a
computation.
Going the other way, an ∃ in a hypothesis is taken apart by pattern
matching: fun ⟨i, hi⟩ => … binds the witness to i and its property to hi.
The tactic spelling of the same move is obtain, in chapter 2.
The second theorem in that block does both moves in one term, and the middle of it is a small tactic script. Read it from the outside in.
fun ⟨i, hi⟩ => …The theorem’s hypothesis is
∃ i, y i = 3, so the proof is a function out of it, and the pattern⟨i, hi⟩takes that proof apart on the way in:iis whichever unit the hypothesis was about,hiis the proof thaty i = 3. Nothing is known aboutibeyondhi.Goaly : Fin 3 → ℝ i : Fin 3 hi : y i = 3 ⊢ ∃ i, 0 < y i⟨i, …⟩The conclusion is again an
∃, so it again needs a witness and a proof. The witness is the unit we were just handed — the samei, because the unit whose outcome is3is exactly the unit whose outcome is positive. That leaves the property0 < y ito prove.Goaly : Fin 3 → ℝ i : Fin 3 hi : y i = 3 ⊢ 0 < y irw [hi]Inside the brackets we switch to tactic mode with
by. The remaining goal mentionsy i, andhisays whaty iis, so rewriting with it replacesy iby3and turns a claim about an unknown outcome into arithmetic.Goaly : Fin 3 → ℝ i : Fin 3 hi : y i = 3 ⊢ 0 < 3norm_num0 < 3is a fact about numerals, which isnorm_num’s job. No goals remain.
That interleaving is normal and worth getting used to: the structure of a
proof is usually clearest as a term, and the arithmetic in its leaves is
usually shortest as a tactic. by is the door between them, and it can be
opened anywhere a proof is expected.
∧ and ∨
theorem and_intro_example (y : Fin 3 → ℝ) (h0 : 0 ≤ y 0) (h1 : 0 ≤ y 1) :
0 ≤ y 0 ∧ 0 ≤ y 1 :=
⟨h0, h1⟩
theorem and_elim_example (y : Fin 3 → ℝ) (h : 0 ≤ y 0 ∧ 0 ≤ y 1) : 0 ≤ y 1 :=
h.2Structurally, p ∧ q is an ordered pair. Build it with ⟨_, _⟩, project out
of it with .1 and .2. There is no more to it.
theorem or_intro_example (i : Fin 3) (h : i ∈ ({0, 1} : Finset (Fin 3))) :
i ∈ ({0, 1} : Finset (Fin 3)) ∨ i = 2 :=
Or.inl h
theorem or_elim_example (y : Fin 3 → ℝ) (h : y 0 = 1 ∨ y 0 = 2) : 0 < y 0 :=
Or.elim h (fun h1 => by rw [h1]; norm_num) (fun h2 => by rw [h2]; norm_num)p ∨ q is the asymmetric one. To prove a disjunction you must say which side
you have: Or.inl for the left, Or.inr for the right. To use one you must
handle both sides, which is what Or.elim takes its two function arguments
for. In this subject the disjunction is almost always “unit or_elim_example is the shape of
every argument about an indicator.
or_intro_example is one application: the hypothesis h already proves the
left disjunct, and Or.inl h labels it as such. The elimination takes more
saying, because its two arguments are two little proofs.
Or.elim h (fun h1 => …) (fun h2 => …)h : y 0 = 1 ∨ y 0 = 2gives no information about which equation holds, so the conclusion0 < y 0has to be established twice, once per side.Or.elimtakes exactly that: a function turning a proof of the left disjunct into the conclusion, and a function doing the same for the right.fun h1 => by rw [h1]; norm_numThe left branch is handed
h1 : y 0 = 1and owes0 < y 0.rw [h1]replacesy 0by1, leaving the numeric goal shown below, andnorm_numsettles it.Goaly : Fin 3 → ℝ h : y 0 = 1 ∨ y 0 = 2 h1 : y 0 = 1 ⊢ 0 < 1fun h2 => by rw [h2]; norm_numThe right branch is the same two tactics against the other equation:
rw [h2]turns the goal into0 < 2, andnorm_numagain finishes. Both branches produce a proof of the same proposition, which is whyOr.elimcan return one regardless of which side was true.Goaly : Fin 3 → ℝ h : y 0 = 1 ∨ y 0 = 2 h2 : y 0 = 2 ⊢ 0 < 2
¬
theorem not_neg_of_nonneg (x : ℝ) (h : 0 ≤ x) : ¬ (x < 0) :=
fun hlt => absurd h (not_le.mpr hlt)
theorem anything_from_contradiction (n : ℕ) (h : 0 < n) (h' : n = 0) : 1 = 2 :=
absurd h (by rw [h']; exact lt_irrefl 0)¬ p is not primitive. It is notation for p → False, where False is the
proposition with no proofs. So a proof of ¬ p is a function turning a proof
of p into a contradiction, and it is introduced with fun like any other
function.
That is the whole of the first theorem. Its conclusion ¬ (x < 0) is an
implication, so the proof begins with fun, and the body has to manufacture a
False out of two facts that cannot both hold.
fun hlt => …¬ (x < 0)is(x < 0) → False, sofunintroduces a hypothesishltasserting the thing we are denying, and what remains to produce isFalse. Together with the theorem’s ownh : 0 ≤ x, the context now contains both halves of a contradiction.Goalx : ℝ h : 0 ≤ x hlt : x < 0 ⊢ Falsenot_le.mpr hltnot_lestates¬ (a ≤ b) ↔ b < a. Ata := 0,b := xthat reads¬ (0 ≤ x) ↔ x < 0, so its right-to-left direction turnshlt : x < 0into a proof of¬ (0 ≤ x)— the negation of exactly the hypothesis we were given.absurd h (not_le.mpr hlt)absurd : p → ¬ p → qconsumes a proof and its negation and returns anything; herepis0 ≤ xandqisFalse. Applying a¬ pto apwould have done the same job —(not_le.mpr hlt) h— andabsurdis the readable spelling.
The second theorem in that block is the one to remember:
theorem anything_from_contradiction (n : ℕ) (h : 0 < n) (h' : n = 0) : 1 = 2
From contradictory hypotheses, anything follows — absurd : p → ¬p → q, with
q arbitrary. A formalization whose hypotheses cannot all hold is provable
and says nothing whatsoever. This is not a hypothetical failure mode; it is the
single most common way an autoformalized theorem is wrong while being green in
the editor, and chapter 5 turns it into a checklist item.
The proof is one application of absurd with a two-tactic script inside it,
and the script is where the two hypotheses are brought into collision.
absurd h …Take
p := 0 < n, whichhproves, andq := (1 = 2), which is the goal —qis unconstrained, so the conclusion never has to be looked at. What is still owed is a proof of¬ (0 < n), and that is the bracket.rw [h']The second hypothesis
h' : n = 0is the other half of the contradiction. Rewriting with it inside the goal¬ (0 < n)replacesnby0, so what has to be denied is now a closed arithmetic statement rather than one about an unknownn.Goaln : ℕ h : 0 < n h' : n = 0 ⊢ ¬0 < 0exact lt_irrefl 0lt_irrefl a : ¬ (a < a)is irreflexivity of<, and ata := 0it is literally the goal. Note where the work happened: nothing about1 = 2was ever proved, and nothing could be — the theorem is true only because its hypotheses are jointly impossible.
↔
theorem nonneg_iff_zero_le (x : ℝ) : 0 ≤ x ↔ ¬ (x < 0) :=
Iff.intro (fun h => not_lt.mpr h) (fun h => not_lt.mp h)
theorem use_iff (x : ℝ) (h : 0 ≤ x) : ¬ (x < 0) :=
(nonneg_iff_zero_le x).mp hp ↔ q is a structure with two fields, the two implications. .mp is
left-to-right (modus ponens), .mpr is right-to-left (reverse). You will
use these constantly, because a large fraction of Mathlib’s usable content is
stated as ↔ — sub_eq_zero, not_lt, div_lt_one, Nat.cast_ne_zero — and
picking the right projection is half of applying it.
Which projection you want is decided by the goal, not by the lemma. Both
directions of nonneg_iff_zero_le are one use of the same Mathlib ↔, read
in opposite directions.
Iff.intro (fun h => …) (fun h => …)Iff.introis the anonymous constructor under its own name: the first argument is the forward implication0 ≤ x → ¬ (x < 0), the second the backward one.⟨_, _⟩would have built the same term.fun h => not_lt.mpr hnot_ltstates¬ (a < b) ↔ b ≤ a. The forward direction has to produce¬ (x < 0)fromh : 0 ≤ x, so instantiate the lemma ata := x,b := 0, where it reads¬ (x < 0) ↔ 0 ≤ x. The proposition we have sits on the right and the one we want on the left, which is what.mpris for.fun h => not_lt.mp hThe backward direction is the same instance of the same lemma with the two sides swapping roles, so it is
.mp. This is why an↔in Mathlib is worth twice what an implication is: one lemma, both directions, and the only thing to get right is which projection turns what you have into what you want.
use_iff then shows the same lemma being used rather than proved:
(nonneg_iff_zero_le x).mp h supplies the explicit argument x, so that the
↔ is about this particular real number, and projects out the direction that
takes h : 0 ≤ x to the conclusion.
Implicit and instance arguments
Three kinds of brackets appear in a Lean signature, and the difference is who supplies the argument.
theorem first_eq_first {α : Type} {n : ℕ} (y : Fin (n + 1) → α) : y 0 = y 0 := rfl
#check @first_eq_first
#check @add_comm(x : ℝ) is explicit: you write it. {α : Type} is implicit: Lean works it
out from the other arguments, which is why you write first_eq_first y and
never mention α or n. @ makes every implicit argument explicit again, and
#check @some_lemma is the fastest way to see what a Mathlib lemma really
takes — worth doing before every use of a lemma you have not used before.
def totalOver {M : Type} [AddCommMonoid M] (y : Fin 3 → M) : M := y 0 + y 1 + y 2
#eval totalOver (![1, 2, 3] : Fin 3 → ℕ)
#check @totalOver[AddCommMonoid M] is an instance argument, filled by a search over a
database of registered instances. This is how one ∑ works for ℕ, ℝ,
matrices and functions at once: the notation is defined for any type with a
registered commutative addition, and Lean finds the registration.
Exercises
Every exercise below is a statement with its proof replaced by sorry. Do
them in term mode, with at most a by norm_num tucked inside a bracket, because the point is to feel
that a proof is a term you build rather than a sequence of commands you run.
Tactics are chapter 2.
ExerciseA definition means what it says (warm-up)
Show that effect y1 y0 1 is y1 1 - y0 1: the definition means what it says
at unit 1.
def effect (y1 y0 : Fin 2 → ℚ) (i : Fin 2) : ℚ := y1 i - y0 i
theorem ex_effect_apply (y1 y0 : Fin 2 → ℚ) : effect y1 y0 1 = y1 1 - y0 1 :=
sorryHint
Both sides reduce to the same term once effect is unfolded, so no tactic is
needed at all. The answer is three letters long.
Show solution
def effect (y1 y0 : Fin 2 → ℚ) (i : Fin 2) : ℚ := y1 i - y0 i
theorem ex_effect_apply (y1 y0 : Fin 2 → ℚ) : effect y1 y0 1 = y1 1 - y0 1 :=
rflExerciseCommuting a conjunction (warm-up)
From “unit 0 and unit 1 both benefit” conclude “unit 1 and unit 0 both benefit”.
theorem ex_and_comm (y1 y0 : Fin 2 → ℝ) (h : y0 0 ≤ y1 0 ∧ y0 1 ≤ y1 1) :
y0 1 ≤ y1 1 ∧ y0 0 ≤ y1 0 :=
sorryHint
Take the hypothesis apart with .1 and .2, put the pieces back in the other
order with ⟨_, _⟩. The whole proof is one line and mentions no lemma.
Show solution
theorem ex_and_comm (y1 y0 : Fin 2 → ℝ) (h : y0 0 ≤ y1 0 ∧ y0 1 ≤ y1 1) :
y0 1 ≤ y1 1 ∧ y0 0 ≤ y1 0 :=
⟨h.2, h.1⟩ExerciseExhibiting a unit (warm-up)
Exhibit a unit whose treatment effect is 2. (![1, 5, 2] is the vector with
those three entries, indexed by Fin 3; the two vectors are the treated and
control outcomes.)
theorem ex_exists_effect :
∃ i : Fin 3, (![1, 5, 2] : Fin 3 → ℚ) i - (![1, 3, 2] : Fin 3 → ℚ) i = 2 :=
sorryHint
⟨i, proof⟩, where you have to choose i yourself. Once the witness is fixed
the remaining goal is arithmetic on concrete rationals, which by norm_num
closes.
Show solution
theorem ex_exists_effect :
∃ i : Fin 3, (![1, 5, 2] : Fin 3 → ℚ) i - (![1, 3, 2] : Fin 3 → ℚ) i = 2 :=
⟨1, by norm_num⟩ExerciseConstant treatment effects (core)
A constant treatment effect assumption says every unit has the same effect
c. Deduce that unit 0 and unit 1 have equal effects.
theorem ex_constant_effect (y1 y0 : Fin 3 → ℝ) (c : ℝ) (h : ∀ i, y1 i - y0 i = c) :
y1 0 - y0 0 = y1 1 - y0 1 :=
sorryHint
Apply the ∀ twice, at 0 and at 1. That gives you y1 0 - y0 0 = c and
y1 1 - y0 1 = c, and the goal is the first left-hand side equal to the
second, so the shared c has to be passed through and one of the two equations
has to be turned round first. Eq.trans composes a = b with b = c, and
Eq.symm reverses an equation; both are available in dot notation, as
e.trans e' and e.symm, on any proof of an equality.
Show solution
theorem ex_constant_effect (y1 y0 : Fin 3 → ℝ) (c : ℝ) (h : ∀ i, y1 i - y0 i = c) :
y1 0 - y0 0 = y1 1 - y0 1 :=
(h 0).trans (h 1).symmExerciseEither way, the observed outcome is non-negative (core)
Every unit is either treated or control, and its observed outcome is the corresponding potential outcome. Given that dichotomy, and that both potential outcomes are non-negative, show the observed outcome is too. This is the two-case shape that every design-based argument has.
theorem ex_obs_nonneg (yobs y1 y0 : ℝ) (h1 : 0 ≤ y1) (h0 : 0 ≤ y0)
(h : yobs = y1 ∨ yobs = y0) : 0 ≤ yobs :=
sorryHint
Or.elim h f g wants one function per side; each side gives you an equation
e, and e ▸ h1 rewrites with it in the proof h1. If ▸ is unfamiliar,
write fun e => by rw [e]; exact h1 instead — it is the same thing with
training wheels.
Show solution
theorem ex_obs_nonneg (yobs y1 y0 : ℝ) (h1 : 0 ≤ y1) (h0 : 0 ≤ y0)
(h : yobs = y1 ∨ yobs = y0) : 0 ≤ yobs :=
Or.elim h (fun e => e ▸ h1) (fun e => e ▸ h0)ExerciseZero error means exactly right (core)
“The estimator is exactly right” and “the estimator’s error is zero” are the same statement. Prove the equivalence.
theorem ex_error_iff (est tau : ℝ) : est = tau ↔ est - tau = 0 :=
sorryHint
Two directions, assembled with ⟨_, _⟩ or Iff.intro. Left to right: rewrite
with the hypothesis and use sub_self. Right to left: sub_eq_zero is the
Mathlib lemma, itself an ↔, so you want its .mp projection.
Show solution
theorem ex_error_iff (est tau : ℝ) : est = tau ↔ est - tau = 0 :=
Iff.intro (fun h => by rw [h, sub_self]) (fun h => sub_eq_zero.mp h)ExerciseCurrying, as a proposition (stretch)
A rule that needs two separate facts is the same as a rule that needs their
conjunction. Nothing about numbers here — p, q, r are arbitrary
propositions, so the only moves available are the ones in the table above.
theorem ex_curry (p q r : Prop) : (p → q → r) → (p ∧ q → r) :=
sorryHint
Introduce two hypotheses with fun, then feed the second one’s two halves
(.1 and .2) to the first. One line, no lemma.
Show solution
theorem ex_curry (p q r : Prop) : (p → q → r) → (p ∧ q → r) :=
fun f h => f h.1 h.2ExerciseDouble negation, the easy direction (stretch)
If a design is unbiased, it is not the case that it is biased: from p,
conclude ¬ ¬ p. (The converse, ¬ ¬ p → p, is not provable this way: it
needs classical logic. Lean has it — Classical.byContradiction — but it is an
axiom, not a construction, which is the kind of thing chapter 8’s
#print axioms exists to reveal.)
theorem ex_not_not (p : Prop) : p → ¬ ¬ p :=
sorryHint
Unfold ¬ and the goal ¬ ¬ p is (p → False) → False. That is an implication,
so it is introduced by fun, and after two funs there is exactly one term in
context that can be applied to another.
Show solution
theorem ex_not_not (p : Prop) : p → ¬ ¬ p :=
fun hp hnp => hnp hpReading autoformalized Lean
Two failures you can already diagnose, with nothing in them but this chapter.
Both failures share a shape, and it is the shape to look for: the statement is true, and the truth has nothing to do with the mathematics you asked about. Lean will not warn you, because there is nothing wrong. The only defense is reading the statement, which is why this chapter came before the one on how to prove things.
Next: the goal state, and the tactics that move it.