Induction2
Varying the Induction Hypothesis
Theorem double_injective: ∀ n m,
double n = double m → n = m. The way we start this proof is a bit delicate: if we begin with
intros n. induction n. all is well. But if we begin it with
intros n m. induction n. we get stuck in the middle of the inductive case... The strategy of doing fewer intros before an induction to obtain a more general IH doesn't always work by itself; sometimes some rearrangement of quantified variables is needed. Suppose, for example, that we wanted to prove double_injective by induction on m instead of n.
Theorem double_injective_FAILED : ∀ n m,
double n = double m →
n = m.
Proof.
intros n m eq. induction n as [| n'].
- (* n = O *) destruct m as [| m'].
+ (* m = O *) reflexivity.
+ (* m = S m' *) simpl double in eq. discriminate eq.
- (* n = S n' *) destruct m as [| m'].
+ (* m = O *) simpl double in eq. discriminate eq.
+ (* m = S m' *) simpl double in eq. injection eq as eq'.
double n = double m →
n = m.
Proof.
intros n m eq. induction n as [| n'].
- (* n = O *) destruct m as [| m'].
+ (* m = O *) reflexivity.
+ (* m = S m' *) simpl double in eq. discriminate eq.
- (* n = S n' *) destruct m as [| m'].
+ (* m = O *) simpl double in eq. discriminate eq.
+ (* m = S m' *) simpl double in eq. injection eq as eq'.
At this point, the induction hypothesis, IHn', does not give us
n' = m' -- there is an extra S in the way -- so the goal is
not provable.
Abort.
What went wrong?
The problem is that, at the point we invoke the induction
hypothesis, we have already introduced m into the context --
intuitively, we have told Coq, "Let's consider some particular n
and m..." and we now have to prove that, if double n = double
m for these particular n and m, then n = m.
The next tactic, induction n says to Coq: We are going to show
the goal by induction on n. That is, we are going to prove, for
all n, that the proposition
holds, by showing
If we look closely at the second statement, it is saying something
rather strange: it says that, for a particular m, if we know
then we can prove
To see why this is strange, let's think of a particular m --
say, 5. The statement is then saying that, if we know
then we can prove
But knowing Q doesn't give us any help at all with proving
R! (If we tried to prove R from Q, we would start with
something like "Suppose double (S n) = 10..." but then we'd be
stuck: knowing that double (S n) is 10 tells us nothing about
whether double n is 10, so Q is useless.)
Trying to carry out this proof by induction on n when m is
already in the context doesn't work because we are then trying to
prove a relation involving every n but just a single m.
The successful proof of double_injective leaves m in the goal
statement at the point where the induction tactic is invoked on
n:
- P n = "if double n = double m, then n = m"
- P O
- P n → P (S n)
- "if double n = double m then n = m"
- "if double (S n) = double m then S n = m".
- Q = "if double n = 10 then n = 5"
- R = "if double (S n) = 10 then S n = 5".
Theorem double_injective : ∀ n m,
double n = double m →
n = m.
Proof.
intros n. induction n as [| n'].
- (* n = O *) intros m eq. simpl double in eq. destruct m as [| m'].
+ (* m = O *) reflexivity.
+ (* m = S m' *) discriminate eq.
- (* n = S n' *) simpl double.
double n = double m →
n = m.
Proof.
intros n. induction n as [| n'].
- (* n = O *) intros m eq. simpl double in eq. destruct m as [| m'].
+ (* m = O *) reflexivity.
+ (* m = S m' *) discriminate eq.
- (* n = S n' *) simpl double.
Notice that both the goal and the induction hypothesis are
different this time: the goal asks us to prove something more
general (i.e., to prove the statement for every m), but the IH
is correspondingly more flexible, allowing us to choose any m we
like when we apply the IH.
intros m eq.
Now we've chosen a particular m and introduced the assumption
that double n = double m. Since we are doing a case analysis on
n, we also need a case analysis on m to keep the two "in sync."
destruct m as [| m'].
+ (* m = O *)
discriminate eq.
+ (* m = S m' *)
+ (* m = O *)
discriminate eq.
+ (* m = S m' *)
At this point, since we are in the second branch of the destruct
m, the m' mentioned in the context is the predecessor of the
m we started out talking about. Since we are also in the S
branch of the induction, this is perfect: if we instantiate the
generic m in the IH with the current m' (this instantiation is
performed automatically by the apply in the next step), then
IHn' gives us exactly what we need to finish the proof.
injection eq as goal.
apply IHn' in goal.
rewrite goal.
reflexivity.
Qed.
What you should take away from all this is that we need to be
careful about using induction to try to prove something too
specific: To prove a property of n and m by induction on n,
it is sometimes important to leave m generic.
In an informal proof, you should simply be careful about what is
given and what isn't. If you're deliberately delaying
introducing a variable, it's good to be explicit. For example, in
an informal analogue of the above, we might say, "by induction on
n, leaving m general".
A brief pause, for a theorem that'll come in handy. The
injectivity of constructors allows us to reason that ∀ (n m
: nat), S n = S m → n = m. The converse of this implication is
an instance of a more general fact about both constructors and
functions, which we will find convenient in a few places below:
Theorem f_equal : ∀ (A B : Type) (f: A → B) (x y: A),
x = y → f x = f y.
Proof. intros A B f x y eq. rewrite eq. reflexivity. Qed.
x = y → f x = f y.
Proof. intros A B f x y eq. rewrite eq. reflexivity. Qed.
Exercise: 1 star, standard, optional (eqb_false_iff)
The following theorem is an alternate "negative" formulation of eqb_true_iff that is more convenient in certain situations (we'll see examples in later chapters). Hint: look at not_P__P_true.
Theorem plus_n_Sm : ∀ n m : nat,
S (n + m) = n + (S m).
Proof.
(* WORKED IN CLASS *)
intros n m. induction n as [| n' IHn'].
- (* n = 0 *)
simpl plus. reflexivity.
- (* n = S n' *)
simpl plus. rewrite → IHn'. reflexivity.
Qed.
S (n + m) = n + (S m).
Proof.
(* WORKED IN CLASS *)
intros n m. induction n as [| n' IHn'].
- (* n = 0 *)
simpl plus. reflexivity.
- (* n = S n' *)
simpl plus. rewrite → IHn'. reflexivity.
Qed.
Both eqb_true and plus_n_Sm are functions involving two
variables and equality, so why does the former need a general
hypothesis and not the latter?
The general rule is that you need a general IH when:
The problem with general rules is that they are, well, general. We
can be more specific about when you'll need a general IH:
That's very specific! Let's look.
- There is more than one variable.
- Variables other than the IH will have different values in different cases.
- When more than one variable changes during recursion in one of your definitions.
The only problem with the more specific explanation is that it's,
well, clearly not right! Looking back at double_injective, our
definition of double only has one argument, but we needed to
vary our hypothesis! What gives?
Print double.
Check double_injective.
(* double_injective
: forall n m : nat, double n = double m -> n = m
*)
Check double_injective.
(* double_injective
: forall n m : nat, double n = double m -> n = m
*)
Look closely at eqb_true_iff. We could have equivalently stated
double_injecive as:
Theorem double_injective_eqb
: ∀ n m : nat, eqb (double n) (double m) = true → eqb n m = true. A proof that n = m on nats n and m is low key also about eqb.
But then why don't we need for plus_n_Sm, which is also about
equality on numbers?
Theorem plus_n_Sm : ∀ n m : nat,
eqb (S (n + m)) (n + (S m)) = true. While it's true that both sides of eqb change here, a change in n without a change in m is enough for eqb to make a recursive call. So: only one variable changes during recursion!
Whew: that was a lot. There's another, more practical, way to
figure out when you need to generalize your IH: you get to the
inductive case and the IH and your goal are 'incompatible' because
your IH is about a specific variable.
Theorem double_injective_eqb
: ∀ n m : nat, eqb (double n) (double m) = true → eqb n m = true. A proof that n = m on nats n and m is low key also about eqb.
Theorem plus_n_Sm : ∀ n m : nat,
eqb (S (n + m)) (n + (S m)) = true. While it's true that both sides of eqb change here, a change in n without a change in m is enough for eqb to make a recursive call. So: only one variable changes during recursion!
Exercise: 3 stars, standard (eqb_sym)
Theorem eqb_trans : ∀ n m p,
eqb n m = true →
eqb m p = true →
eqb n p = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
eqb n m = true →
eqb m p = true →
eqb n p = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Generalizing the IH Explicitly
Theorem double_injective_take2_FAILED : ∀ n m,
double n = double m →
n = m.
Proof.
intros n m. induction m as [| m'].
- (* m = O *) simpl double. intros eq. destruct n as [| n'].
+ (* n = O *) reflexivity.
+ (* n = S n' *) discriminate eq.
- (* m = S m' *) intros eq. destruct n as [| n'].
+ (* n = O *) discriminate eq.
+ (* n = S n' *) apply f_equal.
(* Stuck again here, just like before. *)
Abort.
double n = double m →
n = m.
Proof.
intros n m. induction m as [| m'].
- (* m = O *) simpl double. intros eq. destruct n as [| n'].
+ (* n = O *) reflexivity.
+ (* n = S n' *) discriminate eq.
- (* m = S m' *) intros eq. destruct n as [| n'].
+ (* n = O *) discriminate eq.
+ (* n = S n' *) apply f_equal.
(* Stuck again here, just like before. *)
Abort.
The problem is that, to do induction on m, we must first
introduce n. (If we simply say induction m without
introducing anything first, Coq will automatically introduce n
for us!)
What can we do about this? One possibility is to rewrite the
statement of the lemma so that m is quantified before n. This
works, but it's not nice: We don't want to have to twist the
statements of lemmas to fit the needs of a particular strategy for
proving them! Rather we want to state them in the clearest and
most natural way.
What we can do instead is to first introduce all the quantified
variables and then re-generalize one or more of them,
selectively taking variables out of the context and putting them
back at the beginning of the goal. The generalize dependent
tactic does this.
Theorem double_injective_take2 : ∀ n m,
double n = double m →
n = m.
Proof.
intros n m.
(* n and m are both in the context *)
generalize dependent n.
(* Now n is back in the goal and we can do induction on
m and get a sufficiently general IH. *)
induction m as [| m'].
- (* m = O *) simpl double. intros n eq. destruct n as [| n'].
+ (* n = O *) reflexivity.
+ (* n = S n' *) discriminate eq.
- (* m = S m' *) intros n eq. destruct n as [| n'].
+ (* n = O *) discriminate eq.
+ (* n = S n' *) simpl double. apply f_equal.
apply IHm'. injection eq as goal. apply goal.
Qed.
double n = double m →
n = m.
Proof.
intros n m.
(* n and m are both in the context *)
generalize dependent n.
(* Now n is back in the goal and we can do induction on
m and get a sufficiently general IH. *)
induction m as [| m'].
- (* m = O *) simpl double. intros n eq. destruct n as [| n'].
+ (* n = O *) reflexivity.
+ (* n = S n' *) discriminate eq.
- (* m = S m' *) intros n eq. destruct n as [| n'].
+ (* n = O *) discriminate eq.
+ (* n = S n' *) simpl double. apply f_equal.
apply IHm'. injection eq as goal. apply goal.
Qed.
Let's look at an informal proof of this theorem. Note that
the proposition we prove by induction leaves n quantified,
corresponding to the use of generalize dependent in our formal
proof.
Theorem: For any nats n and m, if double n = double m, then
n = m.
Proof: Let m be a nat. We prove by induction on m that, for
any n, if double n = double m then n = m.
- First, suppose m = 0, and suppose n is a number such
that double n = double m. We must show that n = 0.
- Second, suppose m = S m' and that n is again a number such
that double n = double m. We must show that n = S m', with
the induction hypothesis that for every number s, if double s =
double m' then s = m'.
Exercise: 3 stars, standard, especially useful (gen_dep_practice)
Prove this by induction on l.
Theorem nth_error_after_last: ∀ (n : nat) (X : Type) (l : list X),
length l = n →
nth_error l n = None.
Proof.
(* FILL IN HERE *) Admitted.
☐
length l = n →
nth_error l n = None.
Proof.
(* FILL IN HERE *) Admitted.
☐
(* Write an informal proof of nth_error_after_last in a comment.
You can use language like "by induction on _, leaving _ general"
instead of saying tactic names like "generalize dependent". *)
(* Do not modify the following line: *)
Definition manual_grade_for_nth_error_after_last_informal : option (nat×string) := None.
☐
You can use language like "by induction on _, leaving _ general"
instead of saying tactic names like "generalize dependent". *)
(* Do not modify the following line: *)
Definition manual_grade_for_nth_error_after_last_informal : option (nat×string) := None.
☐
To vary or not to vary
Exercise: 2 stars, standard (complementary_complementary')
Let's prove that two versions of the complementary predicate are equivalent.
You defined this yourself, but let's prove things about our
definition.
Definition complementary' (dna1 dna2 : strand) : bool :=
eq_strand dna1 (map complement dna2).
Lemma complementary_complementary' : ∀ dna1 dna2,
complementary dna1 dna2 = complementary' dna1 dna2.
Proof.
(* FILL IN HERE *) Admitted.
☐
eq_strand dna1 (map complement dna2).
Lemma complementary_complementary' : ∀ dna1 dna2,
complementary dna1 dna2 = complementary' dna1 dna2.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 1 star, standard, optional (complement_correct)
It's always good to check that your functions do what you think they do! Does our complementary predicate agree with our definition of complement?
Lemma complement_correct : ∀ (dna : strand),
complementary dna (map complement dna) = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
complementary dna (map complement dna) = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Lemma eq_strand_iff : ∀ (dna1 dna2 : strand),
eq_strand dna1 dna2 = true ↔ dna1 = dna2.
Proof.
(* FILL IN HERE *) Admitted.
☐
eq_strand dna1 dna2 = true ↔ dna1 = dna2.
Proof.
(* FILL IN HERE *) Admitted.
☐
- intros: move hypotheses/variables from goal to context
- reflexivity: finish the proof (when the goal looks like e =
e)
- apply: prove goal using a hypothesis, lemma, or constructor
- apply... in H: apply a hypothesis, lemma, or constructor to
a hypothesis in the context (forward reasoning)
- apply... with...: explicitly specify values for variables
that cannot be determined by pattern matching
- simpl: simplify computations in the goal
- simpl in H: ... or a hypothesis
- rewrite: use an equality hypothesis (or lemma) to rewrite
the goal
- rewrite ... in H: ... or a hypothesis
- symmetry: changes a goal of the form t=u into u=t
- symmetry in H: changes a hypothesis of the form t=u into
u=t
- unfold: replace a defined constant by its right-hand side in
the goal
- unfold ... in H: ... or a hypothesis
- fold: replace a defined constant's right hand side with the
constant in the goal
- fold ... in ...: ... or a hypothesis
- step ...: A convenience for our one-two unfold/fold
trick.
- step ... in ...: ... in an hypothesis.
- destruct... as...: case analysis on values of inductively
defined types
- destruct... eqn:...: specify the name of an equation to be
added to the context, recording the result of the case
analysis
- induction... as...: induction on values of inductively
defined types
- injection: reason by injectivity of constructors
- discriminate: reason by disjointness of constructors
- ∃ ...: provide an example to prove an existentially-quantified goal
- assert (H: e) (or assert (e) as H): introduce a "local
lemma" e and call it H
- generalize dependent x: move the variable x (and anything else that depends on it) from the context back to an explicit hypothesis in the goal formula
(* 2022-01-12 10:20 *)