Day15_induction2
Varying the Induction Hypothesis
Theorem double_injective: ∀ n m,
double n = double m → n = m.
intros n. induction n.
intros n m. induction 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 in eq. discriminate eq.
- (* n = S n' *) destruct m as [| m'].
+ (* m = O *) simpl in eq. discriminate eq.
+ (* m = S m' *) simpl 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 in eq. destruct m as [| m'].
+ (* m = O *) reflexivity.
+ (* m = S m' *) discriminate eq.
- (* n = S n' *) simpl.
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 *) simpl.
The 0 case is trivial:
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.
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. reflexivity.
- (* n = S n' *)
simpl. 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
*)
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.
Theorem plus_n_Sm : ∀ n m : nat,
eqb (S (n + m)) (n + (S m)) = true.
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. 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. 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. 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, recommended (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.
☐
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 (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.
☐
Programming with Propositions
- If l is the empty list, then x cannot occur on it, so the property "x appears in l" is simply false.
- Otherwise, l has the form x' :: l'. In this case, x occurs in l if either it is equal to x' or it occurs in l'.
Fixpoint In {A : Type} (x : A) (l : list A) : Prop :=
match l with
| [] ⇒ False
| x' :: l' ⇒ x' = x ∨ In x l'
end.
When In is applied to a concrete list, it expands into a
concrete sequence of nested disjunctions.
Example In_example_1 : In 4 [1; 2; 3; 4; 5].
Proof.
(* WORKED IN CLASS *)
simpl. right. right. right. left. reflexivity.
Qed.
Example In_example_2 :
∀ n, In n [2; 4] →
∃ n', n = 2 × n'.
Proof.
(* WORKED IN CLASS *)
simpl.
intros n [H | [H | []]].
- ∃ 1. rewrite <- H. reflexivity.
- ∃ 2. rewrite <- H. reflexivity.
Qed.
(Notice the use of the empty pattern to discharge the last case
en passant.)
We can also prove more generic, higher-level lemmas about In.
Note, in the next, how In starts out applied to a variable and
only gets expanded when we do case analysis on this variable:
Lemma In_map :
∀ (A B : Type) (f : A → B) (l : list A) (x : A),
In x l →
In (f x) (map f l).
Proof.
intros A B f l x.
induction l as [|x' l' IHl'].
- (* l = nil, contradiction *)
simpl. intros [].
- (* l = x' :: l' *)
simpl. intros [H | H].
+ rewrite H. left. reflexivity.
+ right. apply IHl'. apply H.
Qed.
This way of defining propositions recursively, though convenient
in some cases, also has some drawbacks. In particular, it is
subject to Coq's usual restrictions regarding the definition of
recursive functions, e.g., the requirement that they be "obviously
terminating." In the next chapter, we will see how to define
propositions inductively, a different technique with its own set
of strengths and limitations.
Exercise: 2 stars, standard (In_map_iff)
Lemma In_map_iff :
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) ↔
∃ x, f x = y ∧ In x l.
Proof.
intros A B f l y. split.
(* FILL IN HERE *) Admitted.
☐
∀ (A B : Type) (f : A → B) (l : list A) (y : B),
In y (map f l) ↔
∃ x, f x = y ∧ In x l.
Proof.
intros A B f l y. split.
(* FILL IN HERE *) Admitted.
☐
Lemma In_app_iff : ∀ A l l' (a:A),
In a (l++l') ↔ In a l ∨ In a l'.
Proof.
(* FILL IN HERE *) Admitted.
☐
In a (l++l') ↔ In a l ∨ In a l'.
Proof.
(* FILL IN HERE *) Admitted.
☐
Fixpoint flat_map {X Y: Type} (f: X → list Y) (l: list X)
: (list Y) :=
match l with
| [] ⇒ []
| h :: t ⇒ (f h) ++ (flat_map f t)
end.
Lemma In_flat_map:
∀ (A B : Type) (f : A → list B) (l : list A) (y : B),
In y (flat_map f l) → (∃ x : A, In y (f x) ∧ In x l).
Proof.
(* FILL IN HERE *) Admitted.
☐
: (list Y) :=
match l with
| [] ⇒ []
| h :: t ⇒ (f h) ++ (flat_map f t)
end.
Lemma In_flat_map:
∀ (A B : Type) (f : A → list B) (l : list A) (y : B),
In y (flat_map f l) → (∃ x : A, In y (f x) ∧ In x l).
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard, recommended (All)
Recall that functions returning propositions can be seen as properties of their arguments. For instance, if P has type nat → Prop, then P n states that property P holds of n.Fixpoint All {T : Type} (P : T → Prop) (l : list T) : Prop
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Lemma All_In :
∀ T (P : T → Prop) (l : list T),
(∀ x, In x l → P x) ↔
All P l.
Proof.
(* FILL IN HERE *) Admitted.
☐
Fixpoint forallb {X : Type} (test : X → bool) (l : list X) : bool :=
match l with
| [] ⇒ true
| x :: l' ⇒ andb (test x) (forallb test l')
end.
Prove the theorem below, which relates forallb to the All
property of the above exercise.
Theorem forallb_true_iff : ∀ X test (l : list X),
forallb test l = true ↔ All (fun x ⇒ test x = true) l.
Proof.
(* FILL IN HERE *) Admitted.
Are there any important properties of the function forallb which
are not captured by this specification? That is, if we take All
as a given, would it be possible to define a version of forallb
that (a) allowed us to prove the theorem above, but (b) was 'bad'
in some other way? That is, the forallb function would be
'correct' according to All, but people would agree that it
wasn't a 'good' function in some sense.
(* FILL IN HERE *)
☐
- 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
- 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
- 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
(* Wed Oct 14 15:46:35 PDT 2020 *)