InductionProof by Induction
Separate Compilation
For this Require command to work, Rocq needs to be able to
find a compiled version of the previous chapter (Basics.v).
This compiled version, called Basics.vo, is analogous to the
.class files compiled from .java source files and the .o
files compiled from .c files.
To compile Basics.v and obtain Basics.vo, first make sure that
the files Basics.v, Induction.v, and _CoqProject are in
the current directory.
The _CoqProject file should contain just the following line:
-Q . LF This maps the current directory (".", which contains Basics.v, Induction.v, etc.) to the prefix (or "logical directory") "LF". Proof General, CoqIDE, and VSCoq read _CoqProject automatically, to find out to where to look for the file Basics.vo corresponding to the library LF.Basics.
Once the files are in place, there are various ways to build
Basics.vo from an IDE, or you can build it from the command
line. From an IDE...
To compile Basics.v from the command line...
Troubleshooting:
-Q . LF This maps the current directory (".", which contains Basics.v, Induction.v, etc.) to the prefix (or "logical directory") "LF". Proof General, CoqIDE, and VSCoq read _CoqProject automatically, to find out to where to look for the file Basics.vo corresponding to the library LF.Basics.
- In Proof General: The compilation can be made to happen
automatically when you submit the Require line above to PG, by
setting the emacs variable coq-compile-before-require to t.
This can also be found in the menu: "Coq" > "Auto Compilation" >
"Compile Before Require".
- In CoqIDE: One thing you can do on all platforms is open
Basics.v; then, in the "Compile" menu, click on "Compile Buffer".
- For VSCode users, open the terminal pane at the bottom and then follow the command line instructions below. (If you downloaded the project setup .tgz file, just doing `make` should build all the code.)
- First, generate a Makefile using the rocq makefile utility,
which comes installed with Rocq. (If you obtained the whole volume as
a single archive, a Makefile should already exist and you can
skip this step.)
rocq makefile -f _CoqProject *.v -o Makefile You should rerun that command whenever you add or remove Rocq files in this directory. - Now you can compile Basics.v by running make with the
corresponding .vo file as a target:
make Basics.vo All files in the directory can be compiled by giving no arguments:
make - Under the hood, make uses the Rocq compiler, rocq compile. You can
also run rocq compile directly:
rocq compile -Q . LF Basics.v - Since make also calculates dependencies between source files
to compile them in the right order, make should generally be
preferred over running rocq compile explicitly. But as a last (but
not terrible) resort, you can simply compile each file manually
as you go. For example, before starting work on the present
chapter, you would need to run the following command:
rocq compile -Q . LF Basics.v Then, once you've finished this chapter, you'd do
rocq compile -Q . LF Induction.v to get ready to work on the next one. If you ever remove the .vo files, you'd need to give both commands again (in that order).
- For many of the alternatives above you need to make sure that
the rocq executable is in your PATH.
- If you get complaints about missing identifiers, it may be
because the "load path" for Rocq is not set up correctly. The
Print LoadPath. command may be helpful in sorting out such
issues.
- When trying to compile a later chapter, if you see a message like
Compiled library Induction makes inconsistent assumptions over
library Basics a common reason is that the library Basics was modified and recompiled without also recompiling Induction which depends on it. Recompile Induction, or everything if too many files are affected (for instance by running make and if even this doesn't work then make clean; make). - If you get complaints about missing identifiers later in this
file it may be because the "load path" for Rocq is not set up
correctly. The Print LoadPath. command may be helpful in
sorting out such issues.
In particular, if you see a message like
Compiled library Foo makes inconsistent assumptions over
library Bar check whether you have multiple installations of Rocq on your machine. It may be that commands (like rocq compile) that you execute in a terminal window are getting a different version of Rocq than commands executed by Proof General or CoqIDE. - One more tip for CoqIDE users: If you see messages like Error: Unable to locate library Basics, a likely reason is inconsistencies between compiling things within CoqIDE vs using rocq from the command line. This typically happens when there are two incompatible versions of Rocq installed on your system (one associated with CoqIDE, and one associated with rocq from the terminal). The workaround for this situation is compiling using CoqIDE only (i.e. choosing "make" from the menu), and avoiding using rocq directly at all.
Proof by Induction
... can't be done in the same simple way. Just applying
reflexivity doesn't work, since the n in n + 0 is an arbitrary
unknown number, so the match in the definition of + can't be
simplified.
Proof.
intros n.
simpl. (* Does nothing! *)
Abort.
intros n.
simpl. (* Does nothing! *)
Abort.
And reasoning by cases using destruct n doesn't get us much
further: the branch of the case analysis where we assume n = 0
goes through just fine, but in the branch where n = S n' for
some n' we get stuck in exactly the same way.
Theorem add_0_r_secondtry : ∀ n:nat,
n + 0 = n.
Proof.
intros n. destruct n as [| n'] eqn:E.
- (* n = 0 *)
reflexivity. (* so far so good... *)
- (* n = S n' *)
simpl. (* ...but here we are stuck again *)
Abort.
n + 0 = n.
Proof.
intros n. destruct n as [| n'] eqn:E.
- (* n = 0 *)
reflexivity. (* so far so good... *)
- (* n = S n' *)
simpl. (* ...but here we are stuck again *)
Abort.
We could use destruct n' to get a bit further, but,
since n can be arbitrarily large, we'll never get all the way
there if we just go on like this.
To prove interesting facts about numbers, lists, and other
inductively defined sets, we often need a more powerful reasoning
principle: induction.
Recall (from a discrete math course, probably) the principle of
induction over natural numbers: If P(n) is some proposition
involving a natural number n and we want to show that P holds for
all numbers n, we can reason like this:
In Rocq, the steps are the same, except we typically encounter them
in reverse order: we begin with the goal of proving P(n) for all
n and apply the induction tactic to break it down into two
separate subgoals: one where we must show P(O) and another where
we must show P(n') → P(S n'). Here's how this works for the
theorem at hand...
- show that P(O) holds;
- show that, for any n', if P(n') holds, then so does P(S n');
- conclude that P(n) holds for all n.
Theorem add_0_r : ∀ n:nat, n + 0 = n.
Proof.
intros n. induction n as [| n' IHn'].
- (* n = 0 *) reflexivity.
- (* n = S n' *) simpl. rewrite → IHn'. reflexivity. Qed.
Proof.
intros n. induction n as [| n' IHn'].
- (* n = 0 *) reflexivity.
- (* n = S n' *) simpl. rewrite → IHn'. reflexivity. Qed.
Like destruct, the induction tactic takes an as...
clause that specifies the names of the variables to be introduced
in the subgoals. Since there are two subgoals, the as... clause
has two parts, separated by a vertical bar, |. (Strictly
speaking, we can omit the as... clause and Rocq will choose names
for us. In practice, this is a bad practice, as Rocq's automatic
choices tend to be confusing.)
In the first subgoal, n is replaced by 0. No new variables
are introduced (so the first part of the as... is empty), and
the goal becomes 0 = 0 + 0, which follows easily by simplification.
In the second subgoal, n is replaced by S n', and the
assumption n' + 0 = n' is added to the context with the name
IHn' (i.e., the Induction Hypothesis for n'). These two names
are specified in the second part of the as... clause. The goal
in this case becomes S n' = (S n') + 0, which simplifies to
S n' = S (n' + 0), which in turn follows from IHn'.
Theorem minus_n_n : ∀ n,
minus n n = 0.
Proof.
(* WORKED IN CLASS *)
intros n. induction n as [| n' IHn'].
- (* n = 0 *)
simpl. reflexivity.
- (* n = S n' *)
simpl. rewrite → IHn'. reflexivity. Qed.
minus n n = 0.
Proof.
(* WORKED IN CLASS *)
intros n. induction n as [| n' IHn'].
- (* n = 0 *)
simpl. reflexivity.
- (* n = S n' *)
simpl. rewrite → IHn'. reflexivity. Qed.
(The use of the intros tactic in these proofs is actually
redundant. When applied to a goal that contains quantified
variables, the induction tactic will automatically move them
into the context as needed.)
Exercise: 2 stars, standard, especially useful (basic_induction)
Prove the following using induction. You might need previously proven results.
Theorem mul_0_r : ∀ n:nat,
n × 0 = 0.
Proof.
(* FILL IN HERE *) Admitted.
Theorem plus_n_Sm : ∀ n m : nat,
S (n + m) = n + (S m).
Proof.
(* FILL IN HERE *) Admitted.
Theorem add_comm : ∀ n m : nat,
n + m = m + n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem add_assoc : ∀ n m p : nat,
n + (m + p) = (n + m) + p.
Proof.
(* FILL IN HERE *) Admitted.
☐
n × 0 = 0.
Proof.
(* FILL IN HERE *) Admitted.
Theorem plus_n_Sm : ∀ n m : nat,
S (n + m) = n + (S m).
Proof.
(* FILL IN HERE *) Admitted.
Theorem add_comm : ∀ n m : nat,
n + m = m + n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem add_assoc : ∀ n m p : nat,
n + (m + p) = (n + m) + p.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard (double_plus)
Consider the following function, which doubles its argument:
Use induction to prove this simple fact about double:
Exercise: 2 stars, standard (eqb_refl)
The following theorem relates the computational equality =? on nat with the definitional equality = on bool.Exercise: 2 stars, standard, optional (even_S)
One inconvenient aspect of our definition of even n is the recursive call on n - 2. This makes proofs about even n harder when done by induction on n, since we may need an induction hypothesis about n - 2. The following lemma gives an alternative characterization of even (S n) that works better with induction:Proofs Within Proofs
Theorem mult_0_plus' : ∀ n m : nat,
(n + 0 + 0) × m = n × m.
Proof.
intros n m.
replace (n + 0 + 0) with n.
- reflexivity.
- rewrite add_comm. simpl. rewrite add_comm. reflexivity.
Qed.
(n + 0 + 0) × m = n × m.
Proof.
intros n m.
replace (n + 0 + 0) with n.
- reflexivity.
- rewrite add_comm. simpl. rewrite add_comm. reflexivity.
Qed.
The tactic replace e1 with e2 tactic introduces two subgoals.
The first subgoal is the same as the one at the point where we
invoke replace, except that e1 is replaced by e2. The
second subgoal is the equality e1 = e2 itself.
As another example, suppose we want to prove that (n + m)
+ (p + q) = (m + n) + (p + q). The only difference between the
two sides of the = is that the arguments m and n to the
first inner + are swapped, so it seems we should be able to use
the commutativity of addition (add_comm) to rewrite one into the
other. However, the rewrite tactic is not very smart about
where it applies the rewrite. There are three uses of + here,
and it turns out that doing rewrite → add_comm will affect only
the outer one...
Theorem plus_rearrange_firsttry : ∀ n m p q : nat,
(n + m) + (p + q) = (m + n) + (p + q).
Proof.
intros n m p q.
(* We just need to swap (n + m) for (m + n)... seems
like add_comm should do the trick! *)
rewrite add_comm.
(* Doesn't work... Rocq rewrites the wrong plus! :-( *)
Abort.
(n + m) + (p + q) = (m + n) + (p + q).
Proof.
intros n m p q.
(* We just need to swap (n + m) for (m + n)... seems
like add_comm should do the trick! *)
rewrite add_comm.
(* Doesn't work... Rocq rewrites the wrong plus! :-( *)
Abort.
To use add_comm at the point where we need it, we can rewrite
n + m to m + n using replace and then prove n + m = m + n
using add_comm.
Theorem plus_rearrange : ∀ n m p q : nat,
(n + m) + (p + q) = (m + n) + (p + q).
Proof.
intros n m p q.
replace (n + m) with (m + n).
- reflexivity.
- rewrite add_comm. reflexivity.
Qed.
(n + m) + (p + q) = (m + n) + (p + q).
Proof.
intros n m p q.
replace (n + m) with (m + n).
- reflexivity.
- rewrite add_comm. reflexivity.
Qed.
Formal vs. Informal Proof
"Informal proofs are algorithms; formal proofs are code."
Theorem add_assoc' : ∀ n m p : nat,
n + (m + p) = (n + m) + p.
Proof. intros n m p. induction n as [| n' IHn']. reflexivity.
simpl. rewrite IHn'. reflexivity. Qed.
n + (m + p) = (n + m) + p.
Proof. intros n m p. induction n as [| n' IHn']. reflexivity.
simpl. rewrite IHn'. reflexivity. Qed.
Rocq is perfectly happy with this. For a human, however, it
is difficult to make much sense of it. We can use comments and
bullets to show the structure a little more clearly...
Theorem add_assoc'' : ∀ n m p : nat,
n + (m + p) = (n + m) + p.
Proof.
intros n m p. induction n as [| n' IHn'].
- (* n = 0 *)
reflexivity.
- (* n = S n' *)
simpl. rewrite IHn'. reflexivity. Qed.
n + (m + p) = (n + m) + p.
Proof.
intros n m p. induction n as [| n' IHn'].
- (* n = 0 *)
reflexivity.
- (* n = S n' *)
simpl. rewrite IHn'. reflexivity. Qed.
... and if you're used to Rocq you might be able to step
through the tactics one after the other in your mind and imagine
the state of the context and goal stack at each point, but if the
proof were even a little bit more complicated this would be next
to impossible.
A (pedantic) mathematician might write the proof something like
this:
The overall form of the proof is basically similar, and of
course this is no accident: Rocq has been designed so that its
induction tactic generates the same sub-goals, in the same
order, as the bullet points that a mathematician would usually
write. But there are significant differences of detail: the
formal proof is much more explicit in some ways (e.g., the use of
reflexivity) but much less explicit in others (in particular,
the "proof state" at any given point in the Rocq proof is
completely implicit, whereas the informal proof reminds the reader
several times where things stand).
Theorem: Addition is commutative.
Proof: (* FILL IN HERE *)
- Theorem: For any n, m and p,
n + (m + p) = (n + m) + p. Proof: By induction on n.- First, suppose n = 0. We must show that
0 + (m + p) = (0 + m) + p. This follows directly from the definition of +. - Next, suppose n = S n', where
n' + (m + p) = (n' + m) + p. We must now show that
(S n') + (m + p) = ((S n') + m) + p. By the definition of +, this follows from
S (n' + (m + p)) = S ((n' + m) + p), which is immediate from the induction hypothesis. Qed.
- First, suppose n = 0. We must show that
Exercise: 2 stars, advanced, optional (add_comm_informal)
Translate your solution for add_comm into an informal proof:
(* Do not modify the following line: *)
Definition manual_grade_for_add_comm_informal : option (nat×string) := None.
☐
Definition manual_grade_for_add_comm_informal : option (nat×string) := None.
☐
Exercise: 2 stars, standard, optional (eqb_refl_informal)
Write an informal proof of the following theorem, using the informal proof of add_assoc as a model. Don't just paraphrase the Rocq tactics into English!
(* Do not modify the following line: *)
Definition manual_grade_for_eqb_refl_informal : option (nat×string) := None.
☐
Definition manual_grade_for_eqb_refl_informal : option (nat×string) := None.
☐
More Exercises
Exercise: 3 stars, standard, especially useful (mul_comm)
Use replace to help prove add_shuffle3. You don't need to use induction yet.
Theorem add_shuffle3 : ∀ n m p : nat,
n + (m + p) = m + (n + p).
Proof.
(* FILL IN HERE *) Admitted.
n + (m + p) = m + (n + p).
Proof.
(* FILL IN HERE *) Admitted.
Now prove commutativity of multiplication. You will probably want
to look for (or define and prove) a "helper" theorem to be used in
the proof of this one. Hint: what is n × (1 + k)?
Exercise: 3 stars, standard, optional (more_exercises)
Take a piece of paper. For each of the following theorems, first think about whether (a) it can be proved using only simplification and rewriting, (b) it also requires case analysis (destruct), or (c) it also requires induction. Write down your prediction. Then fill in the proof. (There is no need to turn in your piece of paper; this is just to encourage you to reflect before you hack!)
Theorem leb_refl : ∀ n:nat,
(n <=? n) = true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem zero_neqb_S : ∀ n:nat,
0 =? (S n) = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem andb_false_r : ∀ b : bool,
andb b false = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem S_neqb_0 : ∀ n:nat,
(S n) =? 0 = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_1_l : ∀ n:nat, 1 × n = n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem all3_spec : ∀ b c : bool,
orb
(andb b c)
(orb (negb b)
(negb c))
= true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_plus_distr_r : ∀ n m p : nat,
(n + m) × p = (n × p) + (m × p).
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_assoc : ∀ n m p : nat,
n × (m × p) = (n × m) × p.
Proof.
(* FILL IN HERE *) Admitted.
☐
(n <=? n) = true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem zero_neqb_S : ∀ n:nat,
0 =? (S n) = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem andb_false_r : ∀ b : bool,
andb b false = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem S_neqb_0 : ∀ n:nat,
(S n) =? 0 = false.
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_1_l : ∀ n:nat, 1 × n = n.
Proof.
(* FILL IN HERE *) Admitted.
Theorem all3_spec : ∀ b c : bool,
orb
(andb b c)
(orb (negb b)
(negb c))
= true.
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_plus_distr_r : ∀ n m p : nat,
(n + m) × p = (n × p) + (m × p).
Proof.
(* FILL IN HERE *) Admitted.
Theorem mult_assoc : ∀ n m p : nat,
n × (m × p) = (n × m) × p.
Proof.
(* FILL IN HERE *) Admitted.
☐
Before you start working on the next exercise, replace the stub
definitions of incr and bin_to_nat, below, with your solution
from Basics. That will make it possible for this file to
be graded on its own.
Fixpoint incr (m:bin) : bin
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Fixpoint bin_to_nat (m:bin) : nat
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Fixpoint bin_to_nat (m:bin) : nat
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
In Basics, we did some unit testing of bin_to_nat, but we
didn't prove its correctness. Now we'll do so.
If you want to change your previous definitions of incr or bin_to_nat
to make the property easier to prove, feel free to do so!
Exercise: 3 stars, standard, especially useful (binary_commute)
Prove that the following diagram commutes:
incr
bin ----------------------> bin
| |
bin_to_nat | | bin_to_nat
| |
v v
nat ----------------------> nat
S
That is, incrementing a binary number and then converting it to
a (unary) natural number yields the same result as first converting
it to a natural number and then incrementing.
Theorem bin_to_nat_pres_incr : ∀ b : bin,
bin_to_nat (incr b) = 1 + bin_to_nat b.
Proof.
(* FILL IN HERE *) Admitted.
☐
bin_to_nat (incr b) = 1 + bin_to_nat b.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard (nat_bin_nat)
Prove that, if we start with any nat, convert it to bin, and
convert it back, we get the same nat which we started with.
Hint: This proof should go through smoothly using the previous
exercise about incr as a lemma. If not, revisit your definitions
of the functions involved and consider whether they are more
complicated than necessary: the shape of a proof by induction will
match the recursive structure of the program being verified, so
make the recursions as simple as possible.
Bin to Nat and Back to Bin (Advanced)
Let's explore why that theorem fails, and how to prove a modified
version of it. We'll start with some lemmas that might seem
unrelated, but will turn out to be relevant.
Prove this lemma about double, which we defined earlier in the
chapter.
Exercise: 2 stars, advanced (double_bin)
Now define a similar doubling function for bin.
Definition double_bin (b:bin) : bin
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Check that your function correctly doubles zero.
Prove this lemma, which corresponds to double_incr.
Lemma double_incr_bin : ∀ b,
double_bin (incr b) = incr (incr (double_bin b)).
Proof.
(* FILL IN HERE *) Admitted.
☐
double_bin (incr b) = incr (incr (double_bin b)).
Proof.
(* FILL IN HERE *) Admitted.
☐
The theorem fails because there are some bin such that we won't
necessarily get back to the original bin, but instead to an
"equivalent" bin. (We deliberately leave that notion undefined
here for you to think about.)
Explain in a comment, below, why this failure occurs. Your
explanation will not be graded, but it's important that you get it
clear in your mind before going on to the next part. If you're
stuck on this, think about alternative implementations of
double_bin that might have failed to satisfy double_bin_zero
yet otherwise seem correct.
(* FILL IN HERE *)
To solve that problem, we can introduce a normalization function
that selects the simplest bin out of all the equivalent
bin. Then we can prove that the conversion from bin to nat and
back again produces that normalized, simplest bin.
Define normalize. You will need to keep its definition as simple
as possible for later proofs to go smoothly. Do not use
bin_to_nat or nat_to_bin, but do use double_bin.
Hint: Structure the recursion such that it always reaches the
end of the bin and process each bit only once. Do not try to
"look ahead" at future bits.
Exercise: 4 stars, advanced (bin_nat_bin)
It would be wise to do some Example proofs to check that your definition of
normalize works the way you intend before you proceed. They won't be graded,
but fill them in below.
(* FILL IN HERE *)
Finally, prove the main theorem. The inductive cases could be a
bit tricky.
Hint: Start by trying to prove the main statement, see where you
get stuck, and see if you can find a lemma -- perhaps requiring
its own inductive proof -- that will allow the main proof to make
progress. We have one lemma for the B0 case (which also makes
use of double_incr_bin) and another for the B1 case.
Theorem bin_nat_bin : ∀ b, nat_to_bin (bin_to_nat b) = normalize b.
Proof.
(* FILL IN HERE *) Admitted.
☐
Proof.
(* FILL IN HERE *) Admitted.
☐
(* 2025-09-02 21:52 *)
