IndPropInductively Defined Propositions
Inductively Defined Propositions
Example: The Collatz Conjecture
Fixpoint div2 (n : nat) : nat :=
match n with
0 ⇒ 0
| 1 ⇒ 0
| S (S n) ⇒ S (div2 n)
end.
Definition csf (n : nat) : nat :=
if even n then div2 n
else (3 × n) + 1.
match n with
0 ⇒ 0
| 1 ⇒ 0
| S (S n) ⇒ S (div2 n)
end.
Definition csf (n : nat) : nat :=
if even n then div2 n
else (3 × n) + 1.
Fail Fixpoint reaches1_in (n : nat) : nat :=
if n =? 1 then 0
else 1 + reaches1_in (csf n).
if n =? 1 then 0
else 1 + reaches1_in (csf n).
You can write this definition in a standard programming language.
This definition is, however, rejected by Coq's termination
checker, since the argument to the recursive call, csf n, is not
"obviously smaller" than n.
Indeed, this isn't just a pointless limitation: functions in Coq
are required to be total, to ensure logical consistency.
Moreover, we can't fix it by devising a more clever termination
checker: deciding whether this particular function is total
would be equivalent to settling the Collatz conjecture!
Another idea could be to express the concept of "eventually
reaches 1 in the Collatz sequence" as an recursively defined
property of numbers Collatz_holds_for : nat → Prop.
Fail Fixpoint Collatz_holds_for (n : nat) : Prop :=
match n with
| 0 ⇒ False
| 1 ⇒ True
| _ ⇒ if even n then Collatz_holds_for (div2 n)
else Collatz_holds_for ((3 × n) + 1)
end.
match n with
| 0 ⇒ False
| 1 ⇒ True
| _ ⇒ if even n then Collatz_holds_for (div2 n)
else Collatz_holds_for ((3 × n) + 1)
end.
This recursive function is also rejected by the termination
checker, since while we can in principle convince Coq that
div2 n is smaller than n, we can't convince it that
(3 × n) + 1 is smaller than n. Since it's definitely not!
Fortunately, there is another way to do it: We can express the
concept "reaches 1 eventually in the Collatz sequence" as an
inductively defined property of numbers. Intuitively, this
property is defined by a set of rules:
So there are three ways to prove that a number n eventually
reaches 1 in the Collatz sequence:
———————————————————— (Chf_one)
Collatz_holds_for 1
———————————————————— (Chf_even)
Collatz_holds_for 2
———————————————————— (Chf_even)
Collatz_holds_for 4
———————————————————— (Chf_even)
Collatz_holds_for 8
———————————————————— (Chf_even)
Collatz_holds_for 16
———————————————————— (Chf_odd)
Collatz_holds_for 5
———————————————————— (Chf_even)
Collatz_holds_for 10
———————————————————— (Chf_odd)
Collatz_holds_for 3
———————————————————— (Chf_even)
Collatz_holds_for 6
———————————————————— (Chf_even)
Collatz_holds_for 12
Formally in Coq, the Collatz_holds_for property is
inductively defined:
(Chf_one) | |
Collatz_holds_for 1 |
even n = true Collatz_holds_for (div2 n) | (Chf_even) |
Collatz_holds_for n |
even n = false Collatz_holds_for ((3 * n) + 1) | (Chf_odd) |
Collatz_holds_for n |
- n is 1;
- n is even and div2 n reaches 1;
- n is odd and (3 × n) + 1 reaches 1.
We can prove that a number reaches 1 by constructing a (finite) derivation using these rules. For instance, here is the derivation proving that 12 reaches 1 (where we left out the evenness/oddness premises):
———————————————————— (Chf_one)
Collatz_holds_for 1
———————————————————— (Chf_even)
Collatz_holds_for 2
———————————————————— (Chf_even)
Collatz_holds_for 4
———————————————————— (Chf_even)
Collatz_holds_for 8
———————————————————— (Chf_even)
Collatz_holds_for 16
———————————————————— (Chf_odd)
Collatz_holds_for 5
———————————————————— (Chf_even)
Collatz_holds_for 10
———————————————————— (Chf_odd)
Collatz_holds_for 3
———————————————————— (Chf_even)
Collatz_holds_for 6
———————————————————— (Chf_even)
Collatz_holds_for 12
Inductive Collatz_holds_for : nat → Prop :=
| Chf_one : Collatz_holds_for 1
| Chf_even (n : nat) : even n = true →
Collatz_holds_for (div2 n) →
Collatz_holds_for n
| Chf_odd (n : nat) : even n = false →
Collatz_holds_for ((3 × n) + 1) →
Collatz_holds_for n.
| Chf_one : Collatz_holds_for 1
| Chf_even (n : nat) : even n = true →
Collatz_holds_for (div2 n) →
Collatz_holds_for n
| Chf_odd (n : nat) : even n = false →
Collatz_holds_for ((3 × n) + 1) →
Collatz_holds_for n.
Example Collatz_holds_for_12 : Collatz_holds_for 12.
Proof.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_odd. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_odd. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_one.
Qed.
Proof.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_odd. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_odd. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_even. reflexivity. simpl.
apply Chf_one.
Qed.
Conjecture collatz : ∀ n, n ≠ 0 → Collatz_holds_for n.
If you succeed in proving this conjecture, you've got a bright
future as a number theorist! But don't spend too long on it --
it's been open since 1937.
Example: Binary relation for comparing numbers
(le_n) | |
le n n |
le n m | (le_S) |
le n (S m) |
Inductive le : nat → nat → Prop :=
| le_n (n : nat) : le n n
| le_S (n m : nat) : le n m → le n (S m).
Notation "n <= m" := (le n m) (at level 70).
Example: Transitive Closure
R x y | (t_step) |
clos_trans R x y |
clos_trans R x y clos_trans R y z | (t_trans) |
clos_trans R x z |
Inductive clos_trans {X: Type} (R: X→X→Prop) : X→X→Prop :=
| t_step (x y : X) :
R x y →
clos_trans R x y
| t_trans (x y z : X) :
clos_trans R x y →
clos_trans R y z →
clos_trans R x z.
| t_step (x y : X) :
R x y →
clos_trans R x y
| t_trans (x y z : X) :
clos_trans R x y →
clos_trans R y z →
clos_trans R x z.
Inductive Person : Type := Sage | Cleo | Ridley | Moss.
Inductive parent_of : Person → Person → Prop :=
po_SC : parent_of Sage Cleo
| po_SR : parent_of Sage Ridley
| po_CM : parent_of Cleo Moss.
Inductive parent_of : Person → Person → Prop :=
po_SC : parent_of Sage Cleo
| po_SR : parent_of Sage Ridley
| po_CM : parent_of Cleo Moss.
The parent_of relation is not transitive, but we can define
an "ancestor of" relation as its transitive closure:
Definition ancestor_of : Person → Person → Prop :=
clos_trans parent_of.
clos_trans parent_of.
Here is a derivation showing that Sage is an ancestor of Moss:
———————————————————(po_SC) ———————————————————(po_CM)
parent_of Sage Cleo parent_of Cleo Moss
—————————————————————(t_step) —————————————————————(t_step)
ancestor_of Sage Cleo ancestor_of Cleo Moss
————————————————————————————————————————————————————(t_trans)
ancestor_of Sage Moss
———————————————————(po_SC) ———————————————————(po_CM)
parent_of Sage Cleo parent_of Cleo Moss
—————————————————————(t_step) —————————————————————(t_step)
ancestor_of Sage Cleo ancestor_of Cleo Moss
————————————————————————————————————————————————————(t_trans)
ancestor_of Sage Moss
Example: Reflexive and Transitive Closure
R x y | (rt_step) |
clos_refl_trans R x y |
(rt_refl) | |
clos_refl_trans R x x |
clos_refl_trans R x y clos_refl_trans R y z | (rt_trans) |
clos_refl_trans R x z |
Definition cs (n m : nat) : Prop := csf n = m.
This Collatz step relation can be used in conjunction with the
reflexive and transitive closure operation to define a Collatz
multi-step (cms) relation, expressing that a number n
reaches another number m in zero or more Collatz steps:
Definition cms n m := clos_refl_trans cs n m.
Conjecture collatz' : ∀ n, n ≠ 0 → cms n 1.
Conjecture collatz' : ∀ n, n ≠ 0 → cms n 1.
Example: Permutations
(perm3_swap12) | |
Perm3 [a;b;c] [b;a;c] |
(perm3_swap23) | |
Perm3 [a;b;c] [a;c;b] |
Perm3 l1 l2 Perm3 l2 l3 | (perm3_trans) |
Perm3 l1 l3 |
————————(perm_swap12) —————————————————————(perm_swap23)
Perm3 [1;2;3] [2;1;3] Perm3 [2;1;3] [2;3;1]
——————————————————————————————(perm_trans) ————————————(perm_swap12)
Perm3 [1;2;3] [2;3;1] Perm [2;3;1] [3;2;1]
—————————————————————————————————————————————————————(perm_trans)
Perm3 [1;2;3] [3;2;1]
Inductive Perm3 {X : Type} : list X → list X → Prop :=
| perm3_swap12 (a b c : X) :
Perm3 [a;b;c] [b;a;c]
| perm3_swap23 (a b c : X) :
Perm3 [a;b;c] [a;c;b]
| perm3_trans (l1 l2 l3 : list X) :
Perm3 l1 l2 → Perm3 l2 l3 → Perm3 l1 l3.
| perm3_swap12 (a b c : X) :
Perm3 [a;b;c] [b;a;c]
| perm3_swap23 (a b c : X) :
Perm3 [a;b;c] [a;c;b]
| perm3_trans (l1 l2 l3 : list X) :
Perm3 l1 l2 → Perm3 l2 l3 → Perm3 l1 l3.
Example: Evenness (yet again)
(ev_0) | |
ev 0 |
ev n | (ev_SS) |
ev (S (S n)) |
———— (ev_0)
ev 0
———————————— (ev_SS)
ev (S (S 0))
———————————————————— (ev_SS)
ev (S (S (S (S 0))))
Inductive ev : nat → Prop :=
| ev_0 : ev 0
| ev_SS (n : nat) (H : ev n) : ev (S (S n)).
| ev_0 : ev 0
| ev_SS (n : nat) (H : ev n) : ev (S (S n)).
We put off discussing syntax so far, but there are both
similarities and a few differences between inductive properties
like ev and inductive types like nat or list:
Inductive list (X:Type) : Type :=
| nil : list X
| cons (x : X) (l : list X) : list X.
Beyond this syntactic distinction, we can think of the inductive
definition of ev as defining a Coq property ev : nat → Prop,
together with two "evidence constructors":
Inductive list (X:Type) : Type :=
| nil : list X
| cons (x : X) (l : list X) : list X.
Check ev_0 : ev 0.
Check ev_SS : ∀ (n : nat), ev n → ev (S (S n)).
Check ev_SS : ∀ (n : nat), ev n → ev (S (S n)).
In fact, Coq also accepts the following equivalent definition of ev:
Module EvPlayground.
Inductive ev : nat → Prop :=
| ev_0 : ev 0
| ev_SS : ∀ (n : nat), ev n → ev (S (S n)).
End EvPlayground.
Inductive ev : nat → Prop :=
| ev_0 : ev 0
| ev_SS : ∀ (n : nat), ev n → ev (S (S n)).
End EvPlayground.
These evidence constructors can be thought of as "primitive evidence of evenness", and they can be used just like proven theorems. In particular, we can use Coq's apply tactic with the constructor names to obtain evidence for ev of particular numbers...
Theorem ev_4 : ev 4.
Proof. apply ev_SS. apply ev_SS. apply ev_0. Qed.
Proof. apply ev_SS. apply ev_SS. apply ev_0. Qed.
... or we can use function application syntax to combine several
constructors:
Theorem ev_4' : ev 4.
Proof. apply (ev_SS 2 (ev_SS 0 ev_0)). Qed.
Proof. apply (ev_SS 2 (ev_SS 0 ev_0)). Qed.
In this way, we can also prove theorems that have hypotheses
involving ev.
Theorem ev_plus4 : ∀ n, ev n → ev (4 + n).
Proof.
intros n. simpl. intros Hn. apply ev_SS. apply ev_SS. apply Hn.
Qed.
Proof.
intros n. simpl. intros Hn. apply ev_SS. apply ev_SS. apply Hn.
Qed.
Constructing Evidence for Permutations
Lemma Perm3_rev : Perm3 [1;2;3] [3;2;1].
Proof.
apply perm3_trans with (l2:=[2;3;1]).
- apply perm3_trans with (l2:=[2;1;3]).
+ apply perm3_swap12.
+ apply perm3_swap23.
- apply perm3_swap12.
Qed.
Proof.
apply perm3_trans with (l2:=[2;3;1]).
- apply perm3_trans with (l2:=[2;1;3]).
+ apply perm3_swap12.
+ apply perm3_swap23.
- apply perm3_swap12.
Qed.
And again we can equivalently use function application syntax to combine several constructors. Note that the Coq type checker can infer not only types, but also nats and lists.
Lemma Perm3_rev' : Perm3 [1;2;3] [3;2;1].
Proof.
apply (perm3_trans _ [2;3;1] _
(perm3_trans _ [2;1;3] _
(perm3_swap12 _ _ _)
(perm3_swap23 _ _ _))
(perm3_swap12 _ _ _)).
Qed.
Proof.
apply (perm3_trans _ [2;3;1] _
(perm3_trans _ [2;1;3] _
(perm3_swap12 _ _ _)
(perm3_swap23 _ _ _))
(perm3_swap12 _ _ _)).
Qed.
So the informal derivation trees we drew above are not too far
from what's happening formally. Formally we're using the evidence
constructors to build evidence trees, similar to the finite trees we
built using the constructors of data types such as nat, list,
binary trees, etc.
Using Evidence in Proofs
In other words, if someone gives us evidence E for the proposition ev n, then we know that E must be one of two things:
- E = ev_0 and n = O, or
- E = ev_SS n' E', where n = S (S n') and E' is evidence for ev n'.
Destructing and Inverting Evidence
Theorem ev_inversion : ∀ (n : nat),
ev n →
(n = 0) ∨ (∃ n', n = S (S n') ∧ ev n').
Proof.
intros n E. destruct E as [ | n' E'] eqn:EE.
- (* E = ev_0 : ev 0 *)
left. reflexivity.
- (* E = ev_SS n' E' : ev (S (S n')) *)
right. ∃ n'. split. reflexivity. apply E'.
Qed.
ev n →
(n = 0) ∨ (∃ n', n = S (S n') ∧ ev n').
Proof.
intros n E. destruct E as [ | n' E'] eqn:EE.
- (* E = ev_0 : ev 0 *)
left. reflexivity.
- (* E = ev_SS n' E' : ev (S (S n')) *)
right. ∃ n'. split. reflexivity. apply E'.
Qed.
Facts like this are often called "inversion lemmas" because they
allow us to "invert" some given information to reason about all
the different ways it could have been derived.
We can use the inversion lemma that we proved above to help
structure proofs:
Theorem evSS_ev : ∀ n, ev (S (S n)) → ev n.
Proof.
intros n E. apply ev_inversion in E. destruct E as [H0|H1].
- discriminate H0.
- destruct H1 as [n' [Hnn' E']]. injection Hnn' as Hnn'.
rewrite Hnn'. apply E'.
Qed.
Proof.
intros n E. apply ev_inversion in E. destruct E as [H0|H1].
- discriminate H0.
- destruct H1 as [n' [Hnn' E']]. injection Hnn' as Hnn'.
rewrite Hnn'. apply E'.
Qed.
Coq provides a handy tactic called inversion that does the work of our inversion lemma and more besides.
Theorem evSS_ev' : ∀ n,
ev (S (S n)) → ev n.
Proof.
intros n E. inversion E as [| n' E' Hnn'].
(* We are in the E = ev_SS n' E' case now. *)
apply E'.
Qed.
ev (S (S n)) → ev n.
Proof.
intros n E. inversion E as [| n' E' Hnn'].
(* We are in the E = ev_SS n' E' case now. *)
apply E'.
Qed.
We can use inversion to re-prove some theorems from Tactics.v.
Theorem inversion_ex1 : ∀ (n m o : nat),
[n; m] = [o; o] → [n] = [m].
Proof.
intros n m o H. inversion H. reflexivity. Qed.
Theorem inversion_ex2 : ∀ (n : nat),
S n = O → 2 + 2 = 5.
Proof.
intros n contra. inversion contra. Qed.
[n; m] = [o; o] → [n] = [m].
Proof.
intros n m o H. inversion H. reflexivity. Qed.
Theorem inversion_ex2 : ∀ (n : nat),
S n = O → 2 + 2 = 5.
Proof.
intros n contra. inversion contra. Qed.
The tactic inversion actually works on any H : P where P is defined Inductively:
- For each constructor of P, make a subgoal where H is
constrained by the form of this constructor.
- Discard contradictory subgoals (such as ev_0 above).
- Generate auxiliary equalities (as with ev_SS above).
Lemma ev_Even_firsttry : ∀ n,
ev n → Even n.
Proof.
(* WORK IN CLASS *) Admitted.
ev n → Even n.
Proof.
(* WORK IN CLASS *) Admitted.
Induction on Evidence
Lemma ev_Even : ∀ n,
ev n → Even n.
Proof.
unfold Even. intros n E.
induction E as [|n' E' IH].
- (* E = ev_0 *)
∃ 0. reflexivity.
- (* E = ev_SS n' E', with IH : Even n' *)
destruct IH as [k Hk]. rewrite Hk.
∃ (S k). simpl. reflexivity.
Qed.
ev n → Even n.
Proof.
unfold Even. intros n E.
induction E as [|n' E' IH].
- (* E = ev_0 *)
∃ 0. reflexivity.
- (* E = ev_SS n' E', with IH : Even n' *)
destruct IH as [k Hk]. rewrite Hk.
∃ (S k). simpl. reflexivity.
Qed.
Case Study: Regular Expressions
Inductive reg_exp (T : Type) : Type :=
| EmptySet
| EmptyStr
| Char (t : T)
| App (r1 r2 : reg_exp T)
| Union (r1 r2 : reg_exp T)
| Star (r : reg_exp T).
| EmptySet
| EmptyStr
| Char (t : T)
| App (r1 r2 : reg_exp T)
| Union (r1 r2 : reg_exp T)
| Star (r : reg_exp T).
Note that this definition is polymorphic: Regular
expressions in reg_exp T describe strings with characters drawn
from T -- which in this exercise we represent as lists with
elements from T.
We connect regular expressions and strings by defining when a regular expression matches some string. Informally this looks as follows:
- The expression EmptySet does not match any string.
- The expression EmptyStr matches the empty string [].
- The expression Char x matches the one-character string [x].
- If re1 matches s1, and re2 matches s2,
then App re1 re2 matches s1 ++ s2.
- If at least one of re1 and re2 matches s,
then Union re1 re2 matches s.
- Finally, if we can write some string s as the concatenation
of a sequence of strings s = s_1 ++ ... ++ s_k, and the
expression re matches each one of the strings s_i,
then Star re matches s.
We can easily translate this intuition into a set of rules, where we write s =~ re to say that s matches re:
(MEmpty) | |
[] =~ EmptyStr |
(MChar) | |
[x] =~ (Char x) |
s1 =~ re1 s2 =~ re2 | (MApp) |
(s1 ++ s2) =~ (App re1 re2) |
s1 =~ re1 | (MUnionL) |
s1 =~ (Union re1 re2) |
s2 =~ re2 | (MUnionR) |
s2 =~ (Union re1 re2) |
(MStar0) | |
[] =~ (Star re) |
s1 =~ re | |
s2 =~ (Star re) | (MStarApp) |
(s1 ++ s2) =~ (Star re) |
This directly corresponds to the following Inductive definition. We use the notation s =~ re in place of exp_match s re. (By "reserving" the notation before defining the Inductive, we can use it in the definition.)
Reserved Notation "s =~ re" (at level 80).
Inductive exp_match {T} : list T → reg_exp T → Prop :=
| MEmpty : [] =~ EmptyStr
| MChar x : [x] =~ (Char x)
| MApp s1 re1 s2 re2
(H1 : s1 =~ re1)
(H2 : s2 =~ re2)
: (s1 ++ s2) =~ (App re1 re2)
| MUnionL s1 re1 re2
(H1 : s1 =~ re1)
: s1 =~ (Union re1 re2)
| MUnionR s2 re1 re2
(H2 : s2 =~ re2)
: s2 =~ (Union re1 re2)
| MStar0 re : [] =~ (Star re)
| MStarApp s1 s2 re
(H1 : s1 =~ re)
(H2 : s2 =~ (Star re))
: (s1 ++ s2) =~ (Star re)
where "s =~ re" := (exp_match s re).
Inductive exp_match {T} : list T → reg_exp T → Prop :=
| MEmpty : [] =~ EmptyStr
| MChar x : [x] =~ (Char x)
| MApp s1 re1 s2 re2
(H1 : s1 =~ re1)
(H2 : s2 =~ re2)
: (s1 ++ s2) =~ (App re1 re2)
| MUnionL s1 re1 re2
(H1 : s1 =~ re1)
: s1 =~ (Union re1 re2)
| MUnionR s2 re1 re2
(H2 : s2 =~ re2)
: s2 =~ (Union re1 re2)
| MStar0 re : [] =~ (Star re)
| MStarApp s1 s2 re
(H1 : s1 =~ re)
(H2 : s2 =~ (Star re))
: (s1 ++ s2) =~ (Star re)
where "s =~ re" := (exp_match s re).
Notice that this clause in our informal definition...
... is not explicitly reflected in the above definition. Do we
need to add something?
(1) Yes, we should add a rule for this.
(2) No, one of the other rules already covers this case.
(3) No, the lack of a rule actually gives us the behavior we
want.
- "The expression EmptySet does not match any string."
Example reg_exp_ex1 : [1] =~ Char 1.
Example reg_exp_ex2 : [1; 2] =~ App (Char 1) (Char 2).
Example reg_exp_ex3 : ¬([1; 2] =~ Char 1).
Proof.
apply MChar.
Qed.
apply MChar.
Qed.
Example reg_exp_ex2 : [1; 2] =~ App (Char 1) (Char 2).
Proof.
apply (MApp [1]).
- apply MChar.
- apply MChar.
Qed.
apply (MApp [1]).
- apply MChar.
- apply MChar.
Qed.
Example reg_exp_ex3 : ¬([1; 2] =~ Char 1).
Proof.
intros H. inversion H.
Qed.
intros H. inversion H.
Qed.
Lemma MStar1 :
∀ T s (re : reg_exp T) ,
s =~ re →
s =~ Star re.
(* WORK IN CLASS *) Admitted.
∀ T s (re : reg_exp T) ,
s =~ re →
s =~ Star re.
(* WORK IN CLASS *) Admitted.
Naturally, proofs about exp_match often require induction (on evidence!).
Fixpoint re_chars {T} (re : reg_exp T) : list T :=
match re with
| EmptySet ⇒ []
| EmptyStr ⇒ []
| Char x ⇒ [x]
| App re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Union re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Star re ⇒ re_chars re
end.
match re with
| EmptySet ⇒ []
| EmptyStr ⇒ []
| Char x ⇒ [x]
| App re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Union re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Star re ⇒ re_chars re
end.
Theorem in_re_match : ∀ T (s : list T) (re : reg_exp T) (x : T),
s =~ re →
In x s →
In x (re_chars re).
Proof.
intros T s re x Hmatch Hin.
induction Hmatch
as [| x'
| s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | s2 re1 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2].
(* WORK IN CLASS *) Admitted.
s =~ re →
In x s →
In x (re_chars re).
Proof.
intros T s re x Hmatch Hin.
induction Hmatch
as [| x'
| s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | s2 re1 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2].
(* WORK IN CLASS *) Admitted.
The remember Tactic
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
Here is a naive first attempt at setting up the
induction. (Note that we are performing induction on
evidence!)
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
- (* MEmpty *)
simpl. intros H. apply H.
simpl. intros H. apply H.
... but most cases get stuck. For MChar, for instance, we
must show
s2 =~ Char x' →
x'::s2 =~ Char x' which is clearly impossible.
s2 =~ Char x' →
x'::s2 =~ Char x' which is clearly impossible.
- (* MChar. *) intros H. simpl. (* Stuck... *)
Abort.
Abort.
The problem here is that induction over a Prop hypothesis only works properly with hypotheses that are "completely general," i.e., ones in which all the arguments are variables, as opposed to more complex expressions like Star re.
Lemma star_app: ∀ T (s1 s2 : list T) (re re' : reg_exp T),
re' = Star re →
s1 =~ re' →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
re' = Star re →
s1 =~ re' →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
This works, but it makes the statement of the lemma a bit ugly.
Fortunately, there is a better way...
Abort.
The tactic remember e as x eqn:Eq causes Coq to (1) replace all occurrences of the expression e by the variable x, and (2) add an equation Eq : x = e to the context. Here's how we can use it to show the above result:
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
remember (Star re) as re' eqn:Eq.
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
remember (Star re) as re' eqn:Eq.
We now have Eq : re' = Star re.
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
- (* MEmpty *) discriminate.
- (* MChar *) discriminate.
- (* MApp *) discriminate.
- (* MUnionL *) discriminate.
- (* MUnionR *) discriminate.
- (* MChar *) discriminate.
- (* MApp *) discriminate.
- (* MUnionL *) discriminate.
- (* MUnionR *) discriminate.
The interesting cases are those that correspond to Star.
- (* MStar0 *)
intros H. apply H.
- (* MStarApp *)
intros H1. rewrite <- app_assoc.
apply MStarApp.
+ apply Hmatch1.
+ apply IH2.
× apply Eq.
× apply H1.
intros H. apply H.
- (* MStarApp *)
intros H1. rewrite <- app_assoc.
apply MStarApp.
+ apply Hmatch1.
+ apply IH2.
× apply Eq.
× apply H1.
Note that the induction hypothesis IH2 on the MStarApp case
mentions an additional premise Star re'' = Star re, which
results from the equality generated by remember.
Qed.
The remainder of this section in the full version of the chapter develops an extended exercise on regular expressions, leading up to a proof of the well-known "pumping lemma."
Case Study: Improving Reflection
Check eqb_eq : ∀ n1 n2, (n1 =? n2) = true ↔ n1 = n2.
However, this can result in some tedium in proof scripts. Consider:
Theorem filter_not_empty_In : ∀ n l,
filter (fun x ⇒ n =? x) l ≠ [] → In n l.
filter (fun x ⇒ n =? x) l ≠ [] → In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
- (* l = nil *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (n =? m) eqn:H.
+ (* n =? m = true *)
intros _. rewrite eqb_eq in H. rewrite H.
left. reflexivity.
+ (* n =? m = false *)
intros H'. right. apply IHl'. apply H'.
Qed.
intros n l. induction l as [|m l' IHl'].
- (* l = nil *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (n =? m) eqn:H.
+ (* n =? m = true *)
intros _. rewrite eqb_eq in H. rewrite H.
left. reflexivity.
+ (* n =? m = false *)
intros H'. right. apply IHl'. apply H'.
Qed.
The first subcase (where n =? m = true) is awkward
because we have to explicitly "switch worlds."
It would be annoying to have to do this kind of thing all the
time.
Following the terminology introduced in Logic, we call this
the "reflection principle for equality on numbers," and we say
that the boolean n =? m is reflected in the proposition n =
m.
We can streamline this sort of reasoning by defining an inductive proposition that yields a better case-analysis principle for n =? m. Instead of generating the assumption (n =? m) = true, which usually requires some massaging before we can use it, this principle gives us right away the assumption we really need: n = m.
Inductive reflect (P : Prop) : bool → Prop :=
| ReflectT (H : P) : reflect P true
| ReflectF (H : ¬P) : reflect P false.
| ReflectT (H : P) : reflect P true
| ReflectF (H : ¬P) : reflect P false.
Notice that the only way to produce evidence for reflect P
true is by showing P and then using the ReflectT constructor.
If we play this reasoning backwards, it says we can extract
evidence for P from evidence for reflect P true.
To put this observation to work, we first prove that the
statements P ↔ b = true and reflect P b are indeed
equivalent. First, the left-to-right implication:
Theorem iff_reflect : ∀ P b, (P ↔ b = true) → reflect P b.
Proof.
(* WORK IN CLASS *) Admitted.
Proof.
(* WORK IN CLASS *) Admitted.
(The right-to-left implication is left as an exercise.)
We begin by recasting the eqb_eq lemma in terms of reflect:
We can think of reflect as a variant of the usual "if and only if" connective; the advantage of reflect is that, by destructing a hypothesis or lemma of the form reflect P b, we can perform case analysis on b while at the same time generating appropriate hypothesis in the two branches (P in the first subgoal and ¬ P in the second).
Let's use reflect to produce a smoother proof of filter_not_empty_In.
Lemma eqbP : ∀ n m, reflect (n = m) (n =? m).
Proof.
intros n m. apply iff_reflect. rewrite eqb_eq. reflexivity.
Qed.
Proof.
intros n m. apply iff_reflect. rewrite eqb_eq. reflexivity.
Qed.
The proof of filter_not_empty_In now goes as follows. Notice how the calls to destruct and rewrite in the earlier proof of this theorem are combined here into a single call to destruct.
Theorem filter_not_empty_In' : ∀ n l,
filter (fun x ⇒ n =? x) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
- (* l = *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (eqbP n m) as [EQnm | NEQnm].
+ (* n = m *)
intros _. rewrite EQnm. left. reflexivity.
+ (* n <> m *)
intros H'. right. apply IHl'. apply H'.
Qed.
filter (fun x ⇒ n =? x) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
- (* l = *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (eqbP n m) as [EQnm | NEQnm].
+ (* n = m *)
intros _. rewrite EQnm. left. reflexivity.
+ (* n <> m *)
intros H'. right. apply IHl'. apply H'.
Qed.