mirror of
https://github.com/saymrwulf/verifying-crypto-with-lean.git
synced 2026-09-04 20:03:41 +00:00
Per the 7-reader didactic panel and BOOK-OVERHAUL-PLAN.md: - front matter: box legend demoted below a lived example (the aha box becomes its own legend entry) - ch04: open on the stake (proof the size of a compiler), new section 'When you get stuck --- and you will' promoting the debugging session, checkpoint now carries the omega cliffhanger forward - ch06: open on the dare (invert 19 mod a 77-digit prime, two-digit numbers only), machine-referee #eval after the Euclid box, checkpoint distills the constant-time trade into one quotable sentence - ch10: cold-open on the 12 GB crash, spec vocabulary re-armed at the summit statement, falsification tryit after the 16p box (the -151 #eval), wall dispatch tied to the cold open, closing paragraph places the certificate in the live log - ch12: opening pyramid figure now carries question marks resolved layer by layer through the chapter (suspense instead of spoiler), kernel-wall rendered as a scene, 'Where you come in' promoted to its own subsection, false 'closing chapter' removed - appendix-toolkit: opens in the design-review room, Drill 7b (parity argument), street assignment close - appendix-walkthroughs: opens at the reader's low point, one-hole-one- paragraph contract, counts replaced by 'trust the folder' - appendix-repo-tour: active three-promise opener, sabotage-the-button tryit, final tour stop at the transparency log Build verified: tectonic clean, 118 pages, zero unresolved refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
409 lines
19 KiB
TeX
409 lines
19 KiB
TeX
\chapter{Tactics: Proving as a Dialogue}
|
||
\label{ch:tactics}
|
||
|
||
\section{From programs to conversations}
|
||
|
||
Here is the problem with everything you learned in the last chapter: a real
|
||
correctness proof for field multiplication --- the theorem this book is
|
||
climbing toward --- would, written as a raw proof program, be a program the
|
||
size of a small compiler. Nobody writes those by hand. (Chapter~\ref{ch:pat}'s
|
||
handcrafted terms were honest work; honest work does not scale.)
|
||
Instead, Lean offers \emph{tactic mode}: an interactive dialogue where you
|
||
issue commands and Lean builds the proof program for you, step by step,
|
||
showing you the remaining work after each move.
|
||
|
||
You enter the dialogue with the keyword \lean{by}:
|
||
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem and_swap (P Q : Prop) : P ∧ Q → Q ∧ P := by
|
||
intro h
|
||
constructor
|
||
· exact h.2
|
||
· exact h.1
|
||
\end{lstlisting}
|
||
|
||
Place your cursor after \lean{by} in the editor and Lean shows the
|
||
\textbf{goal state} --- the exact logical situation at that point:
|
||
|
||
\begin{lstlisting}
|
||
P Q : Prop
|
||
⊢ P ∧ Q → Q ∧ P
|
||
\end{lstlisting}
|
||
|
||
Everything above the turnstile \(\vdash\) is what you \emph{have} (the
|
||
context); the line after it is what you \emph{owe} (the goal). Every tactic
|
||
transforms this picture. After \lean{intro h}, the hypothesis moves above the
|
||
line; after \lean{constructor}, the goal splits in two. Proving becomes a
|
||
game whose board you can always see.
|
||
|
||
\begin{bigidea}
|
||
A tactic proof is a \textbf{recorded conversation with the goal state}. The
|
||
skill of proving is not memorizing tactic names --- it is learning to
|
||
\emph{read the goal state} and recognize which of a handful of moves makes it
|
||
simpler. Below is the core vocabulary; it covers the vast majority of every
|
||
proof in the real Ed25519 development.
|
||
\end{bigidea}
|
||
|
||
\begin{center}
|
||
\begin{tabular}{@{}lll@{}}
|
||
\toprule
|
||
\textbf{Tactic} & \textbf{When the goal looks like...} & \textbf{Effect} \\
|
||
\midrule
|
||
\lean{intro h} & \lean{P → Q}, \ \lean{∀ x, P x} & assume it; name the evidence \\
|
||
\lean{exact e} & anything & finish: \lean{e} is a proof of the goal \\
|
||
\lean{apply f} & \lean{Q}, given \lean{f : P → Q} & reduce the goal to \lean{P} \\
|
||
\lean{constructor} & \lean{P ∧ Q}, \lean{P ↔ Q}, ... & split into pieces \\
|
||
\lean{cases h} & have \lean{h : P ∨ Q} (or \(\wedge\), \lean{∃}) & case analysis on \lean{h} \\
|
||
\lean{rw [eq]} & contains a rewritable subterm & replace using equation \lean{eq} \\
|
||
\lean{simp} & simplifiable clutter & rewrite with a lemma database \\
|
||
\lean{induction n} & \lean{∀ n : Nat, ...} & base case + inductive step \\
|
||
\lean{rfl} & \lean{a = a} after computation & close by computation \\
|
||
\bottomrule
|
||
\end{tabular}
|
||
\end{center}
|
||
|
||
\section{Rewriting: equality as a tool}
|
||
|
||
The workhorse tactic of equational reasoning is \lean{rw} (rewrite). Given a
|
||
proven equation, it replaces one side by the other inside your goal:
|
||
|
||
\begin{lstlisting}[language=Lean]
|
||
example (a b : Nat) (h : a = b) : a + a = b + b := by
|
||
rw [h] -- goal becomes: b + b = b + b, closed by rfl automatically
|
||
\end{lstlisting}
|
||
|
||
Chains of rewrites read like the two-column proofs of school geometry,
|
||
except a machine checks every line. Here is commutativity-and-associativity
|
||
shuffling, Mathlib lemmas by name:
|
||
|
||
\begin{lstlisting}[language=Lean]
|
||
example (a b c : Nat) : a + b + c = c + b + a := by
|
||
rw [Nat.add_comm a b] -- b + a + c = c + b + a
|
||
rw [Nat.add_assoc] -- b + (a + c) = c + b + a
|
||
rw [Nat.add_comm a c] -- b + (c + a) = c + b + a
|
||
rw [← Nat.add_assoc] -- b + c + a = c + b + a
|
||
rw [Nat.add_comm b c] -- done
|
||
\end{lstlisting}
|
||
|
||
The arrow \(\leftarrow\) rewrites right-to-left. Nobody enjoys writing five-line
|
||
shuffles like this, which is exactly why Chapter~\ref{ch:automation}
|
||
introduces \lean{ring} --- but you must \emph{once} feel the manual version to
|
||
understand what the automation is doing on your behalf.
|
||
|
||
\section{Induction: the tactic that conquers infinity}
|
||
|
||
Remember the embarrassment of Chapter~\ref{ch:pat}: \lean{0 + n = n} does not
|
||
hold by computation. Now we can prove it --- by induction, the proof
|
||
technique that inductive types were born for:
|
||
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem zero_add (n : Nat) : 0 + n = n := by
|
||
induction n with
|
||
| zero => rfl -- 0 + 0 = 0: computes
|
||
| succ k ih => rw [Nat.add_succ, ih]
|
||
\end{lstlisting}
|
||
|
||
The \lean{induction} tactic converts a statement about \emph{all} naturals
|
||
into two finite obligations: the statement for \lean{zero}, and the statement
|
||
for \lean{succ k} \emph{assuming it for} \lean{k} (the induction hypothesis
|
||
\lean{ih}). Because every natural number is built from those two
|
||
constructors, the two cases cover infinity.
|
||
|
||
\begin{worked}{the full board of \lean{zero\_add} --- every goal state, by hand}
|
||
Reading a finished tactic proof is like reading a chess score without a
|
||
board. Here is the same proof \emph{with} the board: every goal state
|
||
written out, exactly as the editor would show it. Reproduce this trace on
|
||
paper until the transitions feel inevitable --- it is the core skill of
|
||
this chapter. We prove \lean{0 + n = n}, where \lean{+} recurses on its
|
||
second argument (Chapter~\ref{ch:lean}).
|
||
|
||
\smallskip
|
||
\noindent After \lean{induction n with}, two boards appear.
|
||
\emph{Case} \lean{zero}:
|
||
\begin{lstlisting}
|
||
⊢ 0 + 0 = 0
|
||
\end{lstlisting}
|
||
The second argument is the literal \lean{0}, so the first defining
|
||
equation fires: \lean{0 + 0} \emph{computes} to \lean{0}. Both sides are
|
||
now the same term; \lean{rfl} closes the board. (Compare the worked
|
||
example in Chapter~\ref{ch:pat}: this is the ``computation sees it'' half.)
|
||
|
||
\smallskip
|
||
\noindent\emph{Case} \lean{succ}: the board now carries an assumption ---
|
||
the induction hypothesis:
|
||
\begin{lstlisting}
|
||
k : Nat
|
||
ih : 0 + k = k
|
||
⊢ 0 + (k + 1) = k + 1
|
||
\end{lstlisting}
|
||
The left side is \lean{add 0 (succ k)}; the \emph{second} defining
|
||
equation fires and rewrites it to \lean{succ (0 + k)}. That step is the
|
||
tactic \lean{rw [Nat.add_succ]} (or one layer of
|
||
\lean{simp only [Nat.add]}), and the board becomes:
|
||
\begin{lstlisting}
|
||
k : Nat
|
||
ih : 0 + k = k
|
||
⊢ (0 + k) + 1 = k + 1
|
||
\end{lstlisting}
|
||
Now the subterm \lean{0 + k} is \emph{exactly} the left side of \lean{ih}.
|
||
Rewriting with \lean{rw [ih]} replaces it:
|
||
\begin{lstlisting}
|
||
k : Nat
|
||
ih : 0 + k = k
|
||
⊢ k + 1 = k + 1
|
||
\end{lstlisting}
|
||
Identical sides --- \lean{rw} auto-closes with \lean{rfl}. Done.
|
||
|
||
\smallskip
|
||
Two habits to take from this trace. First, at every step ask ``which
|
||
defining equation \emph{can} fire, and on which subterm?'' --- that
|
||
question, not tactic names, is what drives the proof. Second, notice
|
||
where the induction hypothesis earned its keep: computation alone got
|
||
stuck at \lean{0 + k} (opaque variable --- Chapter~\ref{ch:pat}'s blocked
|
||
reduction), and \lean{ih} is precisely the permission to cross that gap.
|
||
An induction proof is computation, plus permission slips at the stuck
|
||
points.
|
||
\end{worked}
|
||
|
||
\begin{aha}
|
||
Induction is not a new axiom to swallow --- it falls out of the inductive
|
||
definition of \lean{Nat} itself. ``Every \lean{Nat} is \lean{zero} or a
|
||
\lean{succ}'' \emph{is} the license to do case analysis; recursion on the
|
||
structure \emph{is} the induction. Data and proof principle are two views of
|
||
the same declaration. This is Curry--Howard paying rent again.
|
||
\end{aha}
|
||
|
||
\section{When you get stuck --- and you will}
|
||
|
||
No chapter on tactics is honest without this section. The steady march of
|
||
examples above is what proving looks like \emph{afterwards}, cleaned up for
|
||
print. What it looks like \emph{during} is the next box --- and the
|
||
discipline it teaches is worth more than any tactic in the table.
|
||
|
||
\begin{worked}{a debugging session, reconstructed honestly}
|
||
Here is a stuck proof, exactly as it happens to everyone, worked
|
||
through with the discipline this chapter preaches. Goal: every number
|
||
is at most its double --- \lean{∀ n : Nat, n ≤ 2 * n}. Attempt one:
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem le_double (n : Nat) : n ≤ 2 * n := by
|
||
induction n with
|
||
| zero => rfl -- fine: 0 ≤ 0 checks
|
||
| succ k ih => rw [ih] -- ERROR
|
||
\end{lstlisting}
|
||
The error says \lean{rw} failed: \lean{ih} is
|
||
\lean{k ≤ 2 * k} --- an \emph{inequality}, and \lean{rw} rewrites with
|
||
\emph{equations} only. First lesson banked: the tactic wasn't wrong,
|
||
the \emph{move category} was --- inequalities compose by transitivity
|
||
and monotonicity, not substitution. Attempt two, reading the goal
|
||
state first this time:
|
||
\begin{lstlisting}
|
||
k : Nat
|
||
ih : k ≤ 2 * k
|
||
⊢ k + 1 ≤ 2 * (k + 1)
|
||
\end{lstlisting}
|
||
Plan on paper before touching the keyboard: $2(k+1) = 2k + 2$, and from
|
||
\lean{ih}, $k + 1 \le 2k + 1 \le 2k + 2$ ✓ --- a two-step transitivity
|
||
chain. That \emph{plan} is provable many ways
|
||
(\lean{calc}, \lean{Nat.succ_le_succ} plus lemmas)\dots{} at which
|
||
point the honest practitioner pauses: hypotheses and goal are all
|
||
\emph{linear arithmetic}. The entire induction was unnecessary:
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem le_double (n : Nat) : n ≤ 2 * n := by omega
|
||
\end{lstlisting}
|
||
Third lesson, the big one: getting stuck is often the discovery that
|
||
you are proving something in the wrong \emph{fragment} --- and the next
|
||
chapter is precisely the field guide to fragments. Keep the session's
|
||
order of operations, though, because it generalizes: (1) read what the
|
||
error says about the move category; (2) write the goal state and plan
|
||
on paper; (3) only then ask whether a decision procedure owns the whole
|
||
goal. Ten seconds of (3) before an hour of (2) is the professional's
|
||
cheat --- but (2) is what makes you able to survive when (3) says no.
|
||
\end{worked}
|
||
|
||
\begin{pitfall}
|
||
When a proof gets stuck, resist the urge to try random tactics --- the
|
||
formal-methods equivalent of mashing buttons. The goal state is telling you
|
||
something. Three honest questions unstick most situations: (1)~Is the
|
||
statement actually true as written --- check a small example with
|
||
\lean{\#eval}! (2)~Am I missing a hypothesis --- is there an unstated bound or
|
||
nonzero condition? (3)~Is my induction on the right variable? In the real
|
||
projects behind this book, ``the proof is stuck'' was, more often than not,
|
||
the \emph{statement} being subtly wrong --- a truncated subtraction, a missing
|
||
bound. The proof assistant was the messenger.
|
||
\end{pitfall}
|
||
|
||
\section{Structuring real proofs: \texttt{have} and \texttt{calc}}
|
||
|
||
Big proofs are not flat lists of tactics; they are structured arguments with
|
||
named intermediate results. The \lean{have} tactic states and proves a
|
||
stepping stone; \lean{calc} lays out a chain of equalities or inequalities
|
||
the way you would on a whiteboard:
|
||
|
||
\begin{lstlisting}[language=Lean]
|
||
example (a b : Nat) (h : a = 2 * b) : a + a = 4 * b := by
|
||
have h2 : a + a = 2 * a := by rw [Nat.two_mul]
|
||
calc a + a = 2 * a := h2
|
||
_ = 2 * (2 * b) := by rw [h]
|
||
_ = 4 * b := by rw [← Nat.mul_assoc]
|
||
\end{lstlisting}
|
||
|
||
\begin{worked}{planning a \lean{calc} on paper --- backwards from the target}
|
||
Professionals do not type a \lean{calc} top-to-bottom and hope; they plan
|
||
it on paper, usually \emph{backwards}. Watch the method on the real
|
||
reduction identity from the Ed25519 code (Chapter~\ref{ch:modular} proves
|
||
the ingredients): given a 256-bit value split as
|
||
$x = x_{\mathrm{lo}} + 2^{255} x_{\mathrm{hi}}$, the code returns
|
||
$x_{\mathrm{lo}} + 19 x_{\mathrm{hi}}$, and we owe
|
||
\[
|
||
x_{\mathrm{lo}} + 19\, x_{\mathrm{hi}}
|
||
\;\equiv\;
|
||
x \pmod{p}.
|
||
\]
|
||
Plan backwards: the target's right side is $x$, which we only know through
|
||
its \emph{definition}, so the last line of the chain will be
|
||
``$\ldots = x_{\mathrm{lo}} + 2^{255} x_{\mathrm{hi}} = x$ by the split.''
|
||
What could precede it? Something that turns $19$ into $2^{255}$: the fact
|
||
$19 \equiv 2^{255}$, i.e.\ the constant identity $2^{255} - 19 = p
|
||
\equiv 0$. So the middle step is a congruence rewrite, and the paper plan
|
||
reads, bottom to top:
|
||
\[
|
||
\begin{array}{lll}
|
||
\text{(3)} & x_{\mathrm{lo}} + 2^{255} x_{\mathrm{hi}} = x & \text{definition of the split}\\
|
||
\text{(2)} & x_{\mathrm{lo}} + 19\, x_{\mathrm{hi}} \equiv x_{\mathrm{lo}} + 2^{255} x_{\mathrm{hi}} & \text{since } 19 \equiv 2^{255} \pmod p\\
|
||
\text{(1)} & \text{start: } x_{\mathrm{lo}} + 19\, x_{\mathrm{hi}} & \\
|
||
\end{array}
|
||
\]
|
||
Reverse it, and the \lean{calc} types itself --- each line's justification
|
||
was decided \emph{before} any Lean was written. The habit scales: the real
|
||
multiplication proof in \code{dalek-ed25519-verified} was planned exactly
|
||
this way, as a page of paper algebra whose lines became \lean{have}s. When
|
||
you cannot plan the chain on paper, you are not ready to type it; the
|
||
proof assistant checks reasoning, it does not supply it.
|
||
\end{worked}
|
||
|
||
This style is not cosmetic. In the verified field arithmetic you will read
|
||
later, a single multiplication correctness proof is a \lean{calc} chain
|
||
tracking limb products through carries --- dozens of steps, each trivial,
|
||
whose \emph{composition} is the theorem. \lean{have} and \lean{calc} are how
|
||
proofs stay readable at that scale; they are also how they stay
|
||
\emph{maintainable}, because a broken step localizes the damage to one line.
|
||
|
||
There is one more structuring fact worth knowing early, because it saved the
|
||
real project from a crash-course (literally --- see
|
||
Chapter~\ref{ch:honesty}): breaking a proof into small named \lean{have}
|
||
steps also controls the proof assistant's \emph{memory appetite}. A monolithic
|
||
``figure it all out at once'' tactic call over a huge context can consume
|
||
gigabytes; ten targeted steps, pennies each, prove the same thing. Structure
|
||
is not just style --- it is engineering.
|
||
|
||
\begin{tryit}
|
||
Open \code{exercises/Ch04.lean}. It sets up each theorem with the goal state
|
||
drawn in a comment, then asks you to: prove \lean{and_swap} in tactic mode;
|
||
prove \lean{zero_add} \emph{without} peeking above; and repair a broken
|
||
\lean{calc} chain in which exactly one step is wrong. The third
|
||
exercise is secretly the most realistic job training in this book.
|
||
\end{tryit}
|
||
|
||
\section*{Exercises}
|
||
|
||
\exercise{Prove by induction: \lean{∀ n : Nat, n + 0 = n} and
|
||
\lean{∀ n m : Nat, n + succ m = succ (n + m)}. (These are the mirror images
|
||
of the definitional equations --- the ones computation gives you for free ---
|
||
and together they yield commutativity.)}
|
||
|
||
\exercise{Using the previous exercise, prove
|
||
\lean{∀ n m : Nat, n + m = m + n} by induction on \lean{m}. Write out, in
|
||
one prose sentence per case, what each branch of your proof says.}
|
||
|
||
\exercise{Prove \lean{∀ n : Nat, 2 * n = n + n} twice: once with
|
||
\lean{induction}, once with a single \lean{rw} using a Mathlib lemma you find
|
||
yourself (search hint: \lean{exact?} asks Lean to search for you).}
|
||
|
||
\exercise{(Reading) In the goal state
|
||
\lean{h : a < 2\textasciicircum{}51 ⊢ a * 19 < 2\textasciicircum{}56}, no induction is needed --- this is pure
|
||
arithmetic. Which tactic from the table would you \emph{guess} handles it?
|
||
(Answer next chapter; your guess is the point.)}
|
||
|
||
\section*{Solutions and pathways}
|
||
\solutionsintro
|
||
|
||
\solhead{4.1}
|
||
\pathway Before proving anything, predict which facts are \emph{free}:
|
||
addition recurses on its second argument, so any statement whose second
|
||
argument exhibits a constructor should compute. Both requested statements
|
||
--- \lean{n + 0 = n} and \lean{n + succ m = succ (n + m)} --- have
|
||
constructor-shaped second arguments (\lean{0} and \lean{succ m}). The
|
||
honest content of this exercise is noticing that.
|
||
\answer \lean{theorem a : ∀ n : Nat, n + 0 = n := fun n => rfl} --- the
|
||
first defining equation. \lean{theorem b : ∀ n m, n + succ m = succ (n+m)
|
||
:= fun n m => rfl} --- the second. Neither needs induction; both are the
|
||
definitional mirror images of the two facts that \emph{do}
|
||
(\lean{0 + n = n}, \lean{succ n + m = succ (n + m)}), which is exactly the
|
||
asymmetry the worked example traced. If you reached for \lean{induction}
|
||
here, nothing is wrong --- it succeeds --- but recognizing a free fact
|
||
saves you thirty seconds a hundred times a day.
|
||
|
||
\solhead{4.2}
|
||
\pathway Induct on \lean{m} (the variable the recursion consumes on the
|
||
\emph{right} of \lean{n + m}), so the definitional equations fire on one
|
||
side and 4.1's lemmas patch the other. In each case, before touching
|
||
tactics, write the goal and ask: which side steps by definition, which
|
||
needs a lemma?
|
||
\answer
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem add_comm' (n m : Nat) : n + m = m + n := by
|
||
induction m with
|
||
| zero => rw [Nat.add_zero, Nat.zero_add]
|
||
| succ k ih => rw [Nat.add_succ, Nat.succ_add, ih]
|
||
\end{lstlisting}
|
||
Prose, one sentence per case, as requested. \emph{Zero case:} ``$n + 0$ is
|
||
$n$ by definition, and $0 + n$ is $n$ by the mirror lemma, so both sides
|
||
are $n$.'' \emph{Successor case:} ``both sides compute to a successor ---
|
||
the left by the defining equation, the right by the mirror lemma --- and
|
||
under the \lean{succ} the two sides are the induction hypothesis.'' Note
|
||
the architecture: two definitional equations, two mirror lemmas, one
|
||
induction --- commutativity is not one fact but a small \emph{ecosystem},
|
||
and you have now built all of it from bare constructors.
|
||
|
||
\solhead{4.3}
|
||
\pathway For the induction route, the \lean{succ} case needs
|
||
$2(k+1) = (k+1) + (k+1)$ reorganized into the induction hypothesis's shape
|
||
plus successors --- expect two rewrites. For the library route, the skill
|
||
is \emph{search}: place the cursor on the goal and run \lean{exact?}.
|
||
\answer Induction:
|
||
\begin{lstlisting}[language=Lean]
|
||
theorem two_mul' (n : Nat) : 2 * n = n + n := by
|
||
induction n with
|
||
| zero => rfl
|
||
| succ k ih => rw [Nat.mul_succ, ih, Nat.succ_add, Nat.add_succ]
|
||
\end{lstlisting}
|
||
Library: \lean{exact?} finds \lean{Nat.two_mul}, so
|
||
\lean{theorem two_mul'' (n : Nat) : 2 * n = n + n := Nat.two_mul n}. Both
|
||
are legitimate craft: the first when you are building the ecosystem, the
|
||
second when you are using it. Mathlib has over 200{,}000 lemmas ---
|
||
searching \emph{is} a proof technique, and \lean{exact?} is its tactic.
|
||
|
||
\solhead{4.4}
|
||
\pathway The goal mixes a hypothesis bound, multiplication by a
|
||
\emph{constant}, and powers of two --- inspect the table: nothing fits
|
||
perfectly, which is the setup's point. What you want is a decision
|
||
procedure for linear arithmetic.
|
||
\answer The intended guess is \lean{simp} or \lean{rw}-with-lemmas ---
|
||
and the intended discovery is that neither is pleasant. The actual answer,
|
||
one chapter ahead: \lean{omega}, which decides such goals instantly
|
||
because $a \cdot 19$ is \emph{linear} in $a$ (19 is a constant). If you
|
||
guessed \lean{omega} from the table's absence, better still: you have
|
||
started classifying goals by \emph{logical fragment}, which is precisely
|
||
the professional habit Chapter~\ref{ch:automation} installs.
|
||
|
||
\begin{checkpoint}
|
||
You should now be able to: read a goal state (context, turnstile, goal);
|
||
drive the core tactics \lean{intro}, \lean{exact}, \lean{apply},
|
||
\lean{cases}, \lean{rw}, \lean{induction}; structure a multi-step argument
|
||
with \lean{have} and \lean{calc}; and --- most importantly --- when stuck,
|
||
interrogate the \emph{statement} before blaming the proof. One goal from the
|
||
exercises is deliberately still open: the bound
|
||
$a \cdot 19 < 2^{56}$, which no tactic in this chapter's table owns. Carry it
|
||
with you --- the next chapter opens by handing you the tactic that eats it in
|
||
one line.
|
||
\end{checkpoint}
|