Day12_cases
Today is a long day: we're going to teach you several tactics and
proof methods. There are a few more exercises than usual, too. So:
please start early and ask for help if you need it!
Proof by Case Analysis
Theorem plus_1_neq_0_firsttry : ∀ n : nat,
eqb (n + 1) 0 = false.
Proof.
intros n.
simpl. (* does nothing! *)
Abort.
The reason for this is that the definitions of both
eqb and + begin by performing a match on their first
argument. But here, the first argument to + is the unknown
number n and the argument to eqb is the compound
expression n + 1; neither can be simplified.
To make progress, we need to consider the possible forms of n
separately. If n is O, then we can calculate the final result
of eqb (n + 1) 0 and check that it is, indeed, false. And
if n = S n' for some n', then, although we don't know exactly
what number n + 1 yields, we can calculate that, at least, it
will begin with one S, and this is enough to calculate that,
again, eqb (n + 1) 0 will yield false.
The tactic that tells Coq to consider, separately, the cases where
n = O and where n = S n' is called destruct.
Theorem plus_1_neq_0 : ∀ n : nat,
eqb (n + 1) 0 = false.
Proof.
intros n. destruct n as [| n'] eqn:E.
- reflexivity.
- reflexivity. Qed.
The destruct generates two subgoals, which we must then
prove, separately, in order to get Coq to accept the theorem. The
annotation "as [| n']" is called an intro pattern. It tells
Coq what variable names to introduce in each subgoal. In general,
what goes between the square brackets is a list of lists of
names, separated by |. In this case, the first component is
empty, since the O constructor is nullary (it doesn't have any
arguments). The second component gives a single name, n', since
S is a unary constructor.
It's worth contrasting intro patterns with match patterns, i.e.,
what we write in match expressions. A match pattern in a branch
of a match expression names each constructor and its arguments,
with a | before each one, e.g.:
Definition minustwo (n : nat) : nat :=
match n with
| O ⇒ O
| S O ⇒ O
| S (S n') ⇒ n'
end.
In contrast, an intro pattern names only the arguments of each
constructor, leaving out the constructor name itself, as in:
destruct n as [| n'].
You can nest patterns, e.g., to do case analysis on n and n'
simultaneously, write:
destruct n as [| [| n']].
There will be three subgoals: n = O, n = S O, and n = S (S
n').
When doing case analysis with destruct, in each subgoal, Coq
remembers the assumption about n that is relevant for this
subgoal -- either n = 0 or n = S n' for some n'. The eqn:E
annotation tells destruct to give the name E to this equation.
Leaving off the eqn:E annotation causes Coq to elide these
assumptions in the subgoals. This slightly streamlines proofs
where the assumptions are not explicitly used, but it is better
practice to keep them for the sake of documentation, as they can
help keep you oriented when working with the subgoals.
The - signs on the second and third lines are called
bullets, and they mark the parts of the proof that correspond to
each generated subgoal. The proof script that comes after a
bullet is the entire proof for a subgoal. In this example, each
of the subgoals is easily proved by a single use of reflexivity,
which itself performs some simplification -- e.g., the first one
simplifies eqb (S n' + 1) 0 to false by first rewriting (S n'
+ 1) to S (n' + 1), then unfolding eqb, and then simplifying
the match.
Marking cases with bullets is entirely optional: if bullets are
not present, Coq simply asks you to prove each subgoal in
sequence, one at a time. But it is a good idea to use bullets.
For one thing, they make the structure of a proof apparent, making
it more readable. Also, bullets instruct Coq to ensure that a
subgoal is complete before trying to verify the next one,
preventing proofs for different subgoals from getting mixed
up. These issues become especially important in large
developments, where fragile proofs lead to long debugging
sessions.
There are no hard and fast rules for how proofs should be
formatted in Coq -- in particular, where lines should be broken
and how sections of the proof should be indented to indicate their
nested structure. However, if the places where multiple subgoals
are generated are marked with explicit bullets at the beginning of
lines, then the proof will be readable almost no matter what
choices are made about other aspects of layout.
This is also a good place to mention one other piece of somewhat
obvious advice about line lengths. Beginning Coq users sometimes
tend to the extremes, either writing each tactic on its own line
or writing entire proofs on one line. Good style lies somewhere
in the middle. One reasonable convention is to limit yourself to
80-character lines.
The destruct tactic can be used with any inductively defined
datatype. For example, we use it next to prove that boolean
negation is involutive -- i.e., that negation is its own
inverse.
We've encounted our first significant divergence (of many!)
between how Coq and paper proofs work. Coq keeps track of the
context for you, so Coq proof scripts can leave quite a bit
implicit. Paper proofs are meant to communicate an idea from one
human to another, so it's important to check in and make sure
everyone is on the same page---by, for example, saying what each
case is up front.
Finally, note that we're not "transliterating" the Coq: the two
uses of reflexivity in each branch are written slightly
differently in the paper proof.
Definition minustwo (n : nat) : nat :=
match n with
| O ⇒ O
| S O ⇒ O
| S (S n') ⇒ n'
end.
destruct n as [| n'].
destruct n as [| [| n']].
- Theorem: For any natural number n,
eqb (n + 1) 0 = false.- First, suppose n=0. We must show that eqb (0 + 1) 0 = false, which we have by definition (reflexivity).
- Next, suppose n=S n'. We must show that eqb ((S n') + 1) 0 = false, which holds
by the definition of eqb (reflexivity).
Theorem negb_involutive : ∀ b : bool,
negb (negb b) = b.
Proof.
intros b. destruct b eqn:E.
- reflexivity.
- reflexivity. Qed.
Note that the destruct here has no as clause because
none of the subcases of the destruct need to bind any variables,
so there is no need to specify any names. (We could also have
written as [ | ], or as [].) In fact, we can omit the as
clause from any destruct and Coq will fill in variable names
automatically. This is generally considered bad style, since Coq
often makes confusing choices of names when left to its own
devices. In a paper proof, it'd be good to announce that b=false
in the first case and b=true in the second case.
It is sometimes useful to invoke destruct inside a subgoal,
generating yet more proof obligations. In this case, we use
different kinds of bullets to mark goals on different "levels."
For example:
Print complement. (* Just to remind you. *)
Lemma complement_involutive : ∀ (b : base),
complement (complement b) = b.
Proof.
intros b. destruct b eqn:E.
- reflexivity.
- reflexivity.
- reflexivity.
- reflexivity.
Qed.
Theorem andb_commutative : ∀ b c, andb b c = andb c b.
Proof.
intros b c. destruct b eqn:Eb.
- destruct c eqn:Ec.
+ reflexivity.
+ reflexivity.
- destruct c eqn:Ec.
+ reflexivity.
+ reflexivity.
Qed.
Each pair of calls to reflexivity corresponds to the
subgoals that were generated after the execution of the destruct c
line right above it.
Besides - and +, we can use × (asterisk) as a third kind of
bullet. We can also enclose sub-proofs in curly braces, which is
useful in case we ever encounter a proof that generates more than
three levels of subgoals:
Theorem andb_commutative' : ∀ b c, andb b c = andb c b.
Proof.
intros b c. destruct b eqn:Eb.
{ destruct c eqn:Ec.
{ reflexivity. }
{ reflexivity. } }
{ destruct c eqn:Ec.
{ reflexivity. }
{ reflexivity. } }
Qed.
Since curly braces mark both the beginning and the end of a
proof, they can be used for multiple subgoal levels, as this
example shows. Furthermore, curly braces allow us to reuse the
same bullet shapes at multiple levels in a proof:
Theorem andb3_exchange :
∀ b c d, andb (andb b c) d = andb (andb b d) c.
Proof.
intros b c d. destruct b eqn:Eb.
- destruct c eqn:Ec.
{ destruct d eqn:Ed.
- reflexivity.
- reflexivity. }
{ destruct d eqn:Ed.
- reflexivity.
- reflexivity. }
- destruct c eqn:Ec.
{ destruct d eqn:Ed.
- reflexivity.
- reflexivity. }
{ destruct d eqn:Ed.
- reflexivity.
- reflexivity. }
Qed.
As you may have noticed, many proofs perform case analysis on a variable
right after introducing it:
intros x y. destruct y as [|y].
This pattern is so common that Coq provides a shorthand for it: we
can perform case analysis on a variable when introducing it by
using an intro pattern instead of a variable name. For instance,
here is a shorter proof of the plus_1_neq_0 theorem above.
intros x y. destruct y as [|y].
Theorem plus_1_neq_0' : ∀ n : nat,
eqb (n + 1) 0 = false.
Proof.
intros [|n].
- reflexivity.
- reflexivity. Qed.
Theorem andb_commutative'' :
∀ b c, andb b c = andb c b.
Proof.
intros [] [].
- reflexivity.
- reflexivity.
- reflexivity.
- reflexivity.
Qed.
Exercise: 2 stars, standard (andb_true_elim2)
Prove the following claim, marking cases (and subcases) with bullets when you use destruct.Theorem andb_true_elim2 : ∀ b c : bool,
andb b c = true → c = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Logical Connectives
Conjunction
Exercise: 1 star, standard, optional (proj2)
Optional exerises are just good practice. They're worth no points.Theorem and_commut : ∀ P Q : Prop,
P ∧ Q → Q ∧ P.
Proof.
intros P Q [HP HQ].
split.
- (* left *) apply HQ.
- (* right *) apply HP. Qed.
Exercise: 2 stars, standard (and_assoc)
(In the following proof of associativity, notice how the nested intro pattern breaks the hypothesis H : P ∧ (Q ∧ R) down into HP : P, HQ : Q, and HR : R. Finish the proof from there.)Theorem and_assoc : ∀ P Q R : Prop,
P ∧ (Q ∧ R) → (P ∧ Q) ∧ R.
Proof.
intros P Q R [HP [HQ HR]].
(* FILL IN HERE *) Admitted.
☐
Lemma and_example2 :
∀ n m : nat, n = 0 ∧ m = 0 → n + m = 0.
Proof.
(* WORKED IN CLASS *)
intros n m H.
destruct H as [Hn Hm].
rewrite Hn. rewrite Hm.
reflexivity.
Qed.
As usual, we can also destruct H right when we introduce it,
instead of introducing and then destructing it:
Lemma and_example2' :
∀ n m : nat, n = 0 ∧ m = 0 → n + m = 0.
Proof.
intros n m [Hn Hm].
rewrite Hn. rewrite Hm.
reflexivity.
Qed.
You may wonder why we bothered packing the two hypotheses n = 0
and m = 0 into a single conjunction, since we could have also
stated the theorem with two separate premises:
Lemma and_example2'' :
∀ n m : nat, n = 0 → m = 0 → n + m = 0.
Proof.
intros n m Hn Hm.
rewrite Hn. rewrite Hm.
reflexivity.
Qed.
For this theorem, both formulations are fine. But it's
important to understand how to work with conjunctive hypotheses
because conjunctions often arise from intermediate steps in
proofs, especially in bigger developments. We'll see an example on
day 12.
Another important connective is the disjunction, or logical or
of two propositions: A ∨ B is true when either A or B is.
(Alternatively, we can write or A B, where or : Prop → Prop →
Prop.)
Recall that we used left and right to choose which branch of
disjunction to prove. Just as for conjunction, we use destruct
to ask: if we know P ∨ Q, which one holds?
Disjunction
Theorem or_commut : ∀ P Q : Prop,
P ∨ Q → Q ∨ P.
Proof.
intros P Q HPQ. destruct HPQ as [HP | HQ].
- (* left *) right. apply HP.
- (* right *) left. apply HQ. Qed.
Theorem or_commut' : ∀ P Q : Prop,
P ∨ Q → Q ∨ P.
Proof.
intros P Q [HP | HQ].
- (* left *) right. apply HP.
- (* right *) left. apply HQ. Qed.
... and a slightly more interesting example requiring both left
and right:
Lemma zero_or_succ :
∀ n : nat, n = 0 ∨ n = S (pred n).
Proof.
(* WORKED IN CLASS *)
intros [|n].
- left. reflexivity.
- right. reflexivity.
Qed.
Falsehood and Negation
So far, we have mostly been concerned with proving that certain things are true -- addition is commutative, appending lists is associative, etc. Of course, we may also be interested in negative results, showing that certain propositions are not true. In Coq, such negative statements are expressed with the negation operator ¬.Module MyNot.
Definition not (P:Prop) := P → False.
Notation "~ x" := (not x) : type_scope.
Check not.
(* ===> Prop -> Prop *)
End MyNot.
PROP ::= EXPR1 = EXPR2
| forall x : TYPE, PROP
| PROP1 -> PROP2
| PROP1 /\ PROP2
| PROP1 \/ PROP2
| True
| ~ PROP
| False
Since False is a contradictory proposition, the principle of
explosion also applies to it. If we get False into the proof
context, we can use destruct on it to complete
any goal:
Theorem ex_falso_quodlibet : ∀ (P:Prop),
False → P.
Proof.
(* WORKED IN CLASS *)
intros P contra.
destruct contra. Qed.
Theorem not_False :
¬ False.
Proof.
(* We'll learn about this unfold tactic more below. For now, think
of it as a selective simpl. *)
unfold not. intros H. destruct H. (* Or... apply H! *)
Qed.
The Latin ex falso quodlibet means, literally, "from falsehood
follows whatever you like"; this is another common name for the
principle of explosion.
Exercise: 2 stars, standard, optional (not_implies_our_not)
(Optionally) show that Coq's definition of negation implies the intuitive one mentioned above:Fact not_implies_our_not : ∀ (P:Prop),
¬ P → (∀ (Q:Prop), P → Q).
Proof.
(* FILL IN HERE *) Admitted.
☐
Theorem double_neg : ∀ P : Prop,
P → ~~P.
Proof.
(* WORKED IN CLASS *)
intros P H. unfold not. intros G. apply G. apply H.
Qed.
More Exercises
Exercise: 2 stars, standard (boolean_functions)
Use the tactics you have learned so far to prove the following theorem about boolean functions.Theorem identity_fn_applied_twice :
∀ (f : bool → bool),
(∀ (x : bool), f x = x) →
∀ (b : bool), f (f b) = b.
Proof.
(* FILL IN HERE *) Admitted.
Now state and prove a theorem in Coq; call it
negation_fn_applied_twice. It should be similar to the previous
one but where the second hypothesis says that the function f has
the property that f x = negb x.
Just like for definitions, the autograder will reject your program
if you don't define this theorem at the correct type! If you're
having trouble, talk to Prof. Greenberg or a TA.
(* FILL IN HERE *)
(* Do not modify the following line: *)
Definition manual_grade_for_negation_fn_applied_twice : option (nat×string) := None.
☐
Proofs Within Proofs
Theorem mult_0_plus' : ∀ n m : nat,
(0 + n) × m = n × m.
Proof.
intros n m.
assert (H: 0 + n = n). { reflexivity. }
rewrite → H.
reflexivity. Qed.
The assert tactic introduces two sub-goals. The first is
the assertion itself; by prefixing it with H: we name the
assertion H. (We can also name the assertion with as just as
we did above with destruct and induction, i.e., assert (0 + n
= n) as H.) Note that we surround the proof of this assertion
with curly braces { ... }, both for readability and so that,
when using Coq interactively, we can see more easily when we have
finished this sub-proof. The second goal is the same as the one
at the point where we invoke assert except that, in the context,
we now have the assumption H that 0 + n = n. That is,
assert generates one subgoal where we must prove the asserted
fact and a second subgoal where we can use the asserted fact to
make progress on whatever we were trying to prove in the first
place.
The injection and discriminate Tactics
Inductive nat : Type :=
| O
| S (n : nat).
- The constructor S is injective, or one-to-one. That is,
if S n = S m, it must be that n = m.
- The constructors O and S are disjoint. That is, O is not equal to S n for any n.
Theorem S_injective : ∀ (n m : nat),
S n = S m →
n = m.
Proof.
intros n m H1.
(* Our use of assert here 'captures' n: we're proving this just
for the n we have, not for all n (though it's true for all
n). *)
assert (H2: n = pred (S n)). { reflexivity. }
rewrite H2. rewrite H1. reflexivity.
Qed.
This technique can be generalized to any constructor by
writing the equivalent of pred -- i.e., writing a function that
"undoes" one application of the constructor. As a more convenient
alternative, Coq provides a tactic called injection that allows
us to exploit the injectivity of any constructor. Here is an
alternate proof of the above theorem using injection:
By writing injection H as Hmn at this point, we are asking Coq
to generate all equations that it can infer from H using the
injectivity of constructors (in the present example, the equation
n = m). Each such equation is added as a hypothesis (with the
name Hmn in this case) into the context.
injection H as Hnm. apply Hnm.
Qed.
Qed.
Here's a more interesting example that shows how injection can
derive multiple equations at once.
Theorem injection_ex1 : ∀ (n m o : nat),
[n; m] = [o; o] →
[n] = [m].
Proof.
intros n m o H.
(* WORKED IN CLASS *)
injection H as H1 H2.
rewrite H1. rewrite H2. reflexivity.
Qed.
Alternatively, if you just say injection H with no as clause,
then all the equations will be turned into hypotheses at the
beginning of the goal.
Theorem injection_ex2 : ∀ (n m o : nat),
[n; m] = [o; o] →
[n] = [m].
Proof.
intros n m o H.
injection H.
(* WORKED IN CLASS *)
intros H1 H2. rewrite H1. rewrite H2. reflexivity.
Qed.
Example injection_ex3 : ∀ (X : Type) (x y z : X) (l j : list X),
x :: y :: l = z :: j →
j = z :: l →
x = y.
Proof.
(* FILL IN HERE *) Admitted.
☐
x :: y :: l = z :: j →
j = z :: l →
x = y.
Proof.
(* FILL IN HERE *) Admitted.
☐
We can proceed by case analysis on n. The first case is
trivial.
destruct n as [| n'] eqn:E.
- (* n = 0 *)
intros H. reflexivity.
However, the second one doesn't look so simple: assuming eqb 0
(S n') = true, we must show S n' = 0! The way forward is to
observe that the assumption itself is nonsensical:
- (* n = S n' *)
simpl.
If we use discriminate on this hypothesis, Coq confirms
that the subgoal we are working on is impossible and removes it
from further consideration.
intros H. discriminate H.
Qed.
The discriminate tactic is an instance of the logical principle
we met earlier: the principle of explosion.
Theorem discriminate_ex1 : ∀ (n : nat),
S n = O →
2 + 2 = 5.
Proof.
intros n contra. discriminate contra. Qed.
Theorem discriminate_ex2 : ∀ (n m : nat),
false = true →
[n] = [m].
Proof.
intros n m contra. discriminate contra. Qed.
If you find the principle of explosion confusing, remember
that these proofs are not showing that the conclusion of the
statement holds. Rather, they are showing that, if the
nonsensical situation described by the premise did somehow arise,
then the nonsensical conclusion would also follow, because we'd
be living in an inconsistent universe where every statement is
true. We'll explore the principle of explosion in more detail in
the next chapter, Logic.
Exercise: 1 star, standard (discriminate_ex3)
Example discriminate_ex3 :
∀ (X : Type) (x y z : X) (l j : list X),
x :: y :: l = [] →
x = z.
Proof.
(* FILL IN HERE *) Admitted.
☐
∀ (X : Type) (x y z : X) (l j : list X),
x :: y :: l = [] →
x = z.
Proof.
(* FILL IN HERE *) Admitted.
☐
The proposition 0 ≠ 1 is exactly the same as
~(0 = 1), that is not (0 = 1), which unfolds to
(0 = 1) → False. (We use unfold not explicitly here
to illustrate that point, but generally it can be omitted.)
To prove an inequality, we may assume the opposite
equality...
intros contra.
... and deduce a contradiction from it. Here, the
equality O = S O contradicts the disjointness of
constructors O and S, so discriminate takes care
of it.
discriminate contra.
Qed.
Such inequality statements are frequent enough to warrant a
special notation, x ≠ y:
Similarly, since inequality involves a negation, it requires a
little practice to be able to work with it fluently. Here is one
useful trick. If you are trying to prove a goal that is
nonsensical (e.g., the goal state is false = true), apply
ex_falso_quodlibet to change the goal to False. This makes it
easier to use assumptions of the form ¬P that may be available
in the context -- in particular, assumptions of the form
x≠y.
Theorem not_true_is_false : ∀ b : bool,
b ≠ true → b = false.
Proof.
intros b H. destruct b.
- (* b = true *)
unfold not in H.
apply ex_falso_quodlibet.
apply H. reflexivity.
- (* b = false *)
reflexivity.
Qed.
Since reasoning with ex_falso_quodlibet is quite common, Coq
provides a built-in tactic, exfalso, for applying it.
Theorem not_true_is_false' : ∀ b : bool,
b ≠ true → b = false.
Proof.
intros [] H.
- (* b = false *)
unfold not in H.
exfalso. (* <=== *)
apply H. reflexivity.
- (* b = true *) reflexivity.
Qed.
Theorem contradiction_implies_anything : ∀ P Q : Prop,
(P ∧ ¬P) → Q.
Proof.
(* WORKED IN CLASS *)
intros P Q [HP HNA]. unfold not in HNA.
apply HNA in HP. destruct HP. Qed.
Exercise: 1 star, standard, optional (not_both_true_and_false)
Some (optional!) practice with logical connectives.Logical Equivalence
Module MyIff.
Definition iff (P Q : Prop) := (P → Q) ∧ (Q → P).
Notation "P <-> Q" := (iff P Q)
(at level 95, no associativity)
: type_scope.
End MyIff.
Theorem iff_sym : ∀ P Q : Prop,
(P ↔ Q) → (Q ↔ P).
Proof.
(* WORKED IN CLASS *)
intros P Q [HAB HBA].
split.
- (* -> *) apply HBA.
- (* <- *) apply HAB. Qed.
Exercise: 1 star, standard, optional (iff_properties)
Using the above proof that ↔ is symmetric (iff_sym) as a guide, prove that it is also reflexive and transitive.Theorem iff_refl : ∀ P : Prop,
P ↔ P.
Proof.
(* FILL IN HERE *) Admitted.
Theorem iff_trans : ∀ P Q R : Prop,
(P ↔ Q) → (Q ↔ R) → (P ↔ R).
Proof.
(* FILL IN HERE *) Admitted.
☐
Lemma orb_true_iff : ∀ b1 b2,
b1 || b2 = true ↔ b1 = true ∨ b2 = true.
Proof.
intros [].
- simpl. intros b2. split.
+ intros _. left. reflexivity.
+ intros _. reflexivity.
- simpl. intros [].
+ split.
× intros _. right. reflexivity.
× intros _. reflexivity.
+ split.
× intros H. discriminate H.
× intros [H | H].
{ apply H. }
{ apply H. }
Qed.
Lemma or_assoc :
∀ P Q R : Prop, P ∨ (Q ∨ R) ↔ (P ∨ Q) ∨ R.
Proof.
intros P Q R. split.
- intros [H | [H | H]].
+ left. left. apply H.
+ left. right. apply H.
+ right. apply H.
- intros [[H | H] | H].
+ left. apply H.
+ right. left. apply H.
+ right. right. apply H.
Qed.
intros P Q R. split.
- intros [H | [H | H]].
+ left. left. apply H.
+ left. right. apply H.
+ right. apply H.
- intros [[H | H] | H].
+ left. apply H.
+ right. left. apply H.
+ right. right. apply H.
Qed.
We can now use these facts with rewrite and reflexivity to
give smooth proofs of statements involving equivalences. Here is
a ternary version of the orb_true_iff result:
Lemma orb_true_3 :
∀ a b c : bool, a || b || c = true ↔ a = true ∨ b = true ∨ c = true.
Proof.
intros n m p.
rewrite orb_true_iff. rewrite orb_true_iff. rewrite or_assoc.
reflexivity.
Qed.
The apply tactic can also be used with ↔. When given an
equivalence as its argument, apply tries to guess which side of
the equivalence to use.
Lemma apply_iff_example :
∀ a b : bool, a || b = true → a = true ∨ b = true.
Proof.
intros n m H. apply orb_true_iff. apply H.
Qed.
Lemma not_true_iff_false : ∀ b,
b ≠ true ↔ b = false.
Proof.
(* WORKED IN CLASS *)
intros b. split.
- (* -> *) apply not_true_is_false.
- (* <- *)
intros H. rewrite H. intros H'. discriminate H'.
Qed.
It's common to use iff to relate functions to propositions---like
we already saw for orb_true_iff. Here we relate the eq_base
function to the = proposition. All of these are good practice,
but we leave them optional.
Exercise: 1 star, standard, optional (eq_base_refl)
Lemma eq_base_true : ∀ (b1 b2 : base),
eq_base b1 b2 = true → b1 = b2.
Proof.
(* FILL IN HERE *) Admitted.
☐
eq_base b1 b2 = true → b1 = b2.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 1 star, standard, optional (eq_base_iff)
Prove a theorem characterizing equality on DNA bases.
Lemma eq_base_iff : ∀ (b1 b2 : base),
eq_base b1 b2 = true ↔ b1 = b2.
Proof.
(* FILL IN HERE *) Admitted.
☐
eq_base b1 b2 = true ↔ b1 = b2.
Proof.
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard (not_P__P_true)
This lemma is particularly useful for when we have computational characterizations of interesting properties.Exercise: 2 stars, standard (andb_true_iff)
The following lemma relates the conjunction to the corresponding boolean operations.
Lemma andb_true_iff : ∀ b1 b2:bool,
b1 && b2 = true ↔ b1 = true ∧ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
b1 && b2 = true ↔ b1 = true ∧ b2 = true.
Proof.
(* FILL IN HERE *) Admitted.
☐
Using Tactics on Hypotheses
Theorem S_inj : ∀ (n m : nat) (b : bool),
eqb (S n) (S m) = b →
eqb n m = b.
Proof.
intros n m b H. simpl in H. apply H. Qed.
Similarly, apply L in H matches some conditional statement
L (of the form L1 → L2, say) against a hypothesis H in the
context. However, unlike ordinary apply (which rewrites a goal
matching L2 into a subgoal L1), apply L in H matches H
against L1 and, if successful, replaces it with L2.
In other words, apply L in H gives us a form of "forward
reasoning": from L1 → L2 and a hypothesis matching L1, it
produces a hypothesis matching L2. By contrast, apply L is
"backward reasoning": it says that if we know L1→L2 and we are
trying to prove L2, it suffices to prove L1.
Here is a variant of a proof from above, using forward reasoning
throughout instead of backward reasoning.
Theorem silly3' : ∀ (n : nat),
(eqb n 5 = true → eqb (S (S n)) 7 = true) →
true = eqb n 5 →
true = eqb (S (S n)) 7.
Proof.
intros n eq H.
symmetry in H. apply eq in H. symmetry in H.
apply H. Qed.
Forward reasoning starts from what is given (premises,
previously proven theorems) and iteratively draws conclusions from
them until the goal is reached. Backward reasoning starts from
the goal, and iteratively reasons about what would imply the
goal, until premises or previously proven theorems are reached.
If you've seen informal proofs before (for example, in a math
class), they probably used forward reasoning. They might have even
told you that backward reasoning was wrong or not allowed! In
general, idiomatic use of Coq tends to favor backward reasoning,
but in some situations the forward style can be easier to think
about.
Unfolding Definitions
... and try to prove a simple fact about square...
Lemma square_mult : ∀ n m,
(∀ n m o, n × (m × o) = (n × m) × o) → (* we'll prove this later *)
(∀ n m, n × m = m × n) → (* same *)
square (n × m) = square n × square m.
Proof.
intros n m mult_assoc mult_comm.
simpl.
... we get stuck: simpl doesn't simplify anything at this point,
and since we haven't proved any other facts about square, there
is nothing we can apply or rewrite with.
To make progress, we can manually unfold the definition of
square:
Now we have plenty to work with: both sides of the equality are
expressions involving multiplication, and we have lots of facts
about multiplication at our disposal. In particular, we know that
it is commutative and associative, and from these facts it is not
hard to finish the proof.
rewrite mult_assoc.
assert (H : n × m × n = n × n × m).
{ rewrite mult_comm. apply mult_assoc. }
rewrite H. rewrite mult_assoc. reflexivity.
Qed.
At this point, a deeper discussion of unfolding and simplification
is in order.
You may already have observed that tactics like simpl,
reflexivity, and apply will often unfold the definitions of
functions automatically when this allows them to make progress. For
example, if we define foo m to be the constant 5...
then the simpl in the following proof (or the reflexivity, if
we omit the simpl) will unfold foo m to (fun x ⇒ 5) m and
then further simplify this expression to just 5.
However, this automatic unfolding is rather conservative. For
example, if we define a slightly more complicated function
involving a pattern match...
...then the analogous proof will get stuck:
Fact silly_fact_2_FAILED : ∀ m, bar m + 1 = bar (m + 1) + 1.
Proof.
intros m.
simpl. (* Does nothing! *)
Abort.
The reason that simpl doesn't make progress here is that it
notices that, after tentatively unfolding bar m, it is left with
a match whose scrutinee, m, is a variable, so the match cannot
be simplified further. (It is not smart enough to notice that the
two branches of the match are identical.) So it gives up on
unfolding bar m and leaves it alone. Similarly, tentatively
unfolding bar (m+1) leaves a match whose scrutinee is a
function application (that, itself, cannot be simplified, even
after unfolding the definition of +), so simpl leaves it
alone.
At this point, there are two ways to make progress. One is to use
destruct m to break the proof into two cases, each focusing on a
more concrete choice of m (O vs S _). In each case, the
match inside of bar can now make progress, and the proof is
easy to complete.
Fact silly_fact_2 : ∀ m, bar m + 1 = bar (m + 1) + 1.
Proof.
intros m.
destruct m.
- simpl. reflexivity.
- simpl. reflexivity.
Qed.
This approach works, but it depends on our recognizing that the
match hidden inside bar is what was preventing us from making
progress.
A more straightforward way to make progress is to explicitly tell
Coq to unfold bar.
Now it is apparent that we are stuck on the match expressions on
both sides of the =, and we can use destruct to finish the
proof without thinking too hard.
destruct m.
- reflexivity.
- reflexivity.
Qed.
Finally, it's often useful to do the unfold/fold 1-2 punch:
unfolding a recursive definition and then immediately folding
it back up will reveal the work one step of recursion is doing
without doing any simplification.
Fixpoint funny_rec (n : nat) : nat :=
match n with
| O ⇒ 0
| S n' ⇒ if evenb n then 0 else funny_rec n'
end.
Exercise: 2 stars, standard (funny_rec_evenb_0)
In the n = S n' case, notice that simpl gives you an annoying answer, but unfold/fold works a treat.
Lemma funny_rec_evenb_0 : ∀ n,
evenb n = true →
funny_rec n = 0.
Proof.
(* FILL IN HERE *) Admitted.
☐
evenb n = true →
funny_rec n = 0.
Proof.
(* FILL IN HERE *) Admitted.
☐
Using destruct on Compound Expressions
Definition sillyfun (n : nat) : bool :=
if eqb n 3 then false
else if eqb n 5 then false
else false.
Theorem sillyfun_false : ∀ (n : nat),
sillyfun n = false.
Proof.
intros n. unfold sillyfun.
destruct (eqb n 3).
- (* eqb n 3 = true *) reflexivity.
- (* eqb n 3 = false *) destruct (eqb n 5).
+ (* eqb n 5 = true *) reflexivity.
+ (* eqb n 5 = false *) reflexivity. Qed.
After unfolding sillyfun in the above proof, we find that
we are stuck on if (eqb n 3) then ... else .... But either
n is equal to 3 or it isn't, so we can use destruct (eqb
n 3) to let us reason about the two cases.
In general, the destruct tactic can be used to perform case
analysis of the results of arbitrary computations. If e is an
expression whose type is some inductively defined type T, then,
for each constructor c of T, destruct e generates a subgoal
in which all occurrences of e (in the goal and in the context)
are replaced by c.
More practically, we can use these case analyses to reason about
the complex decisions made in programs. Here's a function that
computes the minimum of three arguments. First, let's make sure we
have a good leb defined.
Fixpoint leb (n m : nat) : bool :=
match n with
| O ⇒ true
| S n' ⇒
match m with
| O ⇒ false
| S m' ⇒ leb n' m'
end
end.
Definition min3 (n1 n2 n3 : nat) : nat :=
if leb n1 n2
then if leb n1 n3
then n1
else n3
else if leb n2 n3
then n2
else n3.
Definition argmin3 {A:Type} (cost : A → nat) (o1 o2 o3 : A) : A :=
let c1 := cost o1 in
let c2 := cost o2 in
let c3 := cost o3 in
if leb c1 c2
then if leb c1 c3
then o1
else o3
else if leb c2 c3
then o2
else o3.
Lemma argmin3_min3_eqs : ∀ {A:Type} (cost : A → nat) (o1 o2 o3 : A) (n1 n2 n3 : nat),
cost o1 = n1 →
cost o2 = n2 →
cost o3 = n3 →
cost (argmin3 cost o1 o2 o3) = min3 n1 n2 n3.
Proof.
(* FILL IN HERE *) Admitted.
cost o1 = n1 →
cost o2 = n2 →
cost o3 = n3 →
cost (argmin3 cost o1 o2 o3) = min3 n1 n2 n3.
Proof.
(* FILL IN HERE *) Admitted.
To prove this one, you should be able to just use argmin3_min3_eqs.
Corollary argmin3_min3 : ∀ {A:Type} (cost : A → nat) (o1 o2 o3 : A),
cost (argmin3 cost o1 o2 o3) = min3 (cost o1) (cost o2) (cost o3).
Proof.
(* FILL IN HERE *) Admitted.
☐
cost (argmin3 cost o1 o2 o3) = min3 (cost o1) (cost o2) (cost o3).
Proof.
(* FILL IN HERE *) Admitted.
☐
Now suppose that we want to convince Coq of the (rather
obvious) fact that sillyfun1 n yields true only when n is
odd. By analogy with the proofs we did with sillyfun above, it
is natural to start the proof like this:
Theorem sillyfun1_odd_FAILED : ∀ (n : nat),
(∀ n m, eqb n m = true → n = m) →
sillyfun1 n = true →
oddb n = true.
Proof.
intros n eqb_true eq. unfold sillyfun1 in eq.
destruct (eqb n 3).
(* stuck... *)
Abort.
We get stuck at this point because the context does not
contain enough information to prove the goal! The problem is that
the substitution performed by destruct is too brutal -- it threw
away every occurrence of eqb n 3, but we need to keep some
memory of this expression and how it was destructed, because we
need to be able to reason that, since eqb n 3 = true in this
branch of the case analysis, it must be that n = 3, from which
it follows that n is odd.
What we would really like is to substitute away all existing
occurences of eqb n 3, but at the same time add an equation to
the context that records which case we are in. Recall that the
eqn: qualifier allows us to introduce such an equation, giving
it a name that we choose.
Theorem sillyfun1_odd : ∀ (n : nat),
(∀ n m, eqb n m = true → n = m) →
sillyfun1 n = true →
oddb n = true.
Proof.
intros n eqb_true eq. unfold sillyfun1 in eq.
destruct (eqb n 3) eqn:Heqe3.
(* Now we have the same state as at the point where we got
stuck above, except that the context contains an extra equality
assumption, which is exactly what we need to make progress. *)
- (* e3 = true *) apply eqb_true in Heqe3.
rewrite → Heqe3. reflexivity.
- (* e3 = false *)
(* When we come to the second equality test in the body of
the function we are reasoning about, we can use eqn: again in
the same way, allow us to finish the proof. *)
destruct (eqb n 5) eqn:Heqe5.
+ (* e5 = true *)
apply eqb_true in Heqe5.
rewrite → Heqe5. reflexivity.
+ (* e5 = false *) discriminate eq. Qed.
(* Mon Oct 12 08:48:49 PDT 2020 *)