Require Import Bool.
Require Import List.
Require Import String.
Require Import ZArith.
Require Import Omega.
Require Import List.
Require Import String.
Require Import ZArith.
Require Import Omega.
List provides the cons notation "::":
x :: xs is the same as cons x xs
Fixpoint my_length {A: Type} (l: list A) : nat :=
match l with
| nil => O
| x :: xs => S (my_length xs)
end.
match l with
| nil => O
| x :: xs => S (my_length xs)
end.
List provides the append notation "++":
xs ++ ys is the same as app xs ys
Fixpoint my_rev {A: Type} (l: list A) : list A :=
match l with
| nil => nil
| x :: xs => rev xs ++ x :: nil
end.
match l with
| nil => nil
| x :: xs => rev xs ++ x :: nil
end.
Prop is the type we give to propositions.
Prop basically works the same as Type.
In fact, its type is Type:
Check Prop.
Prop : Type
We use the constructor tactic to ask Coq
to find a constructor that satisfies the goal.
constructor.
Alternatively, we could have explicitly told
Coq exactly which constructor to use with:
exact I.
Qed.
myFalse is also a proposition. It has ZERO constructors.
That means there is no way to produce a value of type myFalse.
Since a proposition P is true only when we can produce a value
of type P, we know that myFalse is in fact false.
Given a proof of myFalse, we can prove anything.
Inversion performs case analysis on a hypothesis.
Suppose our goal is G and we ask Coq to do inversion
on hypothesis H : T. For each constructor C of T
that could have produced H, we get a new subgoal
requiring us to prove G under the assumption that
H = C x1 x2 ... for some arguments x1 x2 ... to C.
Often it will be the case that some constructors of T
could not have produced H. Coq will automatically
dispatch those subgoals for us which greatly simplifies
proofs. This is the primary difference between destruct
and inversion.
For False (the builtin equivalent to myFalse),
there are 0 constructors, so when we perform
inversion, we end up with 0 subgoals and the
proof is done!
inversion H.
Qed.
Qed.
When we write a definition using "Lemma" or
"Theorem", what we're actually doing is giving
a type T and then using tactics to construct a term
of type T.
So far, we generally only do this for types whose
type is in Prop, but we can do it for "normal"
types too:
exact (list nat). exact nat.
Qed.
Having a contradiction in a hypothesis will
let us prove any goal.
discriminate is a tactic that looks
for mismatching constructors in a hypothesis
and uses that contradiction to prove any goal.
It is a less-general version of the congruence
tactic we saw in lecture last time. In Coq,
there are often many tactics that can solve
a particular goal. Sometimes we will use a
less general tactic because it can help a reader
understand our proof and will usually be faster.
Under the hood, 1 looks like S 0 and
2 looks like S (S 0). discriminate will
peel off one S, to get 0 = S 0. Since
0 and S are different constructors of an
inductive type (where all constructors are
distinct) they are not equal, and Coq can
use the contradiction to complete our proof.
discriminate.
Qed.
Qed.
Note that even equality is defined, not builtin.
Here's yo, another definition of an empty type.
We will have to do a little more work to
show that yo is empty though.
well, that didn't work
induction H.
assumption.
assumption.
but that did!
Qed.
Negation in Coq is encoded in the not type.
It sort of works like our yoyo proof above.
Open Scope string_scope.
Open Scope Z_scope.
Open Scope Z_scope.
Now let's build a programming language.
We can define the syntax of a language
as an inductive datatype.
constant expressions, like 3 or 0
program variables, like "x" or "foo"
adding expressions, e1 + e2
multiplying expressions, e1 * e2
comparing expressions, e1 <= e2
On paper, we would typically write this
type down using a "BNF grammar" as:
Coq provides mechanisms to define
your own notation which we can use
to get "concrete syntax".
Feel free to ignore most of this, especially
the "level" and "associativity" stuff.
expr ::= Z | Var | expr + expr | expr * expr | expr <= expr
Coercion Eint : Z >-> expr.
Coercion Evar : string >-> expr.
Notation "X [+] Y" := (Eadd X Y)
(at level 83, left associativity).
Notation "X [*] Y" := (Emul X Y)
(at level 82, left associativity).
Notation "X [<=] Y" := (Elte X Y)
(at level 84, no associativity).
Check (1 [+] 2).
Check ("x" [+] 2).
Check ("x" [+] 2 [<=] "y").
Coercion Evar : string >-> expr.
Notation "X [+] Y" := (Eadd X Y)
(at level 83, left associativity).
Notation "X [*] Y" := (Emul X Y)
(at level 82, left associativity).
Notation "X [<=] Y" := (Elte X Y)
(at level 84, no associativity).
Check (1 [+] 2).
Check ("x" [+] 2).
Check ("x" [+] 2 [<=] "y").
Parsing is a classic CS topic, but won't say
much more about it in this course. Parsing is
still a rich and active research topic, and there
are many decent tools out there to help practioners
build good parsers.
Note that all we've done so far is define the
syntax of expressions in our language. We
have said NOTHING about what these expressions
mean. In upcoming lectures we will spend
some time studying how we can describe the meanings
of programs by giving semantics for our programming
languages.
To make this clear, note that operations like addition
and multiplication are NOT commutative in syntax. They
will only be commutative in the meaning of expressions
later.
The specialize tactic gives concrete
arguments to a forall quantified hypothesis.
specialize (H 0 1).
inversion is smart
inversion H.
Qed.
Qed.
Although we have not yet defined what programs mean,
we can write some functions to analyze their syntax.
Here we simply count the number of Eint subexpressions
in a given expression.
same as S O
same as O
same as plus (nconsts e1) (nconsts e2)
We can also use existential quantifiers in Coq.
To prove an existential, you must provide a
witness, that is, a concrete example of the
type you're existentially quantifying.
Here we give a concrete example.
Now we have to show that the example satisfies the property
simpl. reflexivity.
Qed.
Qed.
Compute the size of an expression.
Fixpoint esize (e: expr) : nat :=
match e with
| Eint _
| Evar _ =>
1
| Eadd e1 e2
| Emul e1 e2
| Elte e1 e2 =>
esize e1 + esize e2
end.
match e with
| Eint _
| Evar _ =>
1
| Eadd e1 e2
| Emul e1 e2
| Elte e1 e2 =>
esize e1 + esize e2
end.
Notice how we grouped similar cases together
in the definition of esize. This is just
sugar, you can see the full definition with:
esize = fix esize (e : expr) : nat := match e with | Eint _ => 1%nat | Evar _ => 1%nat | e1 [+] e2 => (esize e1 + esize e2)%nat | e1 [*] e2 => (esize e1 + esize e2)%nat | e1 [<=] e2 => (esize e1 + esize e2)%nat end : expr -> nat
Lemma nconsts_le_size:
forall e,
(nconsts e <= esize e)%nat.
Proof.
intros.
induction e.
+ simpl. auto.
forall e,
(nconsts e <= esize e)%nat.
Proof.
intros.
induction e.
+ simpl. auto.
The auto tactic will solve many simple
goals, including those that reflexivity
would solve. auto also has the property
that it will never fail. If it cannot
solve your goal, then it just does nothing.
This will be particularly useful when we
start chaining together sequences of tactics
to operate simultaneously over multiple subgoals.
+ simpl. auto.
The omega will solve many arithemetic goals.
Unlike auto, omega will fail if it cannot
solve your goal.
+ simpl. omega.
+ simpl. omega.
+ simpl. omega.
Qed.
+ simpl. omega.
+ simpl. omega.
Qed.
that proof had a lot of copy-pasta :(
Here we see our first "tactic combinator",
the powerful semicolon ";".
For any tactics a and b, a; b runs
a on the goal and then runs b on all
of the subgoals generate by a.
We can chain tactics toghether in this
way to make shorter, more automated proofs.
In the case of this lemma, we'd like to:
do induction, then on every resulting subgoal do simpl, then on every resulting subgoal do auto, then on every resulting subgoal do omega
induction e; simpl; auto; omega.
Note that after the auto,
only the Eadd, Emul, and Elte subgoals remain,
but it's hard to tell since
the proof does not "pause".
Qed.
Notice how sometime we have to use the scope
specifier "
Locate "<=".
This generates a lot of output. In this file
we really only care about the nat and Z
entries:
We can also print the definition of le:
"x <= y" := Z.le x y : Z_scope (default interpretation) "n <= m" := le n m : nat_scope
Inductive le (n : nat) : nat -> Prop := | le_n : (n <= n)%nat | le_S : forall m : nat, (n <= m)%nat -> (n <= S m)%nat
Inductive has_const : expr -> Prop :=
| hc_in :
forall c, has_const (Eint c)
| hc_add_l :
forall e1 e2,
has_const e1 ->
has_const (Eadd e1 e2)
| hc_add_r :
forall e1 e2,
has_const e2 ->
has_const (Eadd e1 e2)
| hc_mul_l :
forall e1 e2,
has_const e1 ->
has_const (Emul e1 e2)
| hc_mul_r :
forall e1 e2,
has_const e2 ->
has_const (Emul e1 e2)
| hc_cmp_l :
forall e1 e2,
has_const e1 ->
has_const (Elte e1 e2)
| hc_cmp_r :
forall e1 e2,
has_const e2 ->
has_const (Elte e1 e2).
Similarly, we can define a relation
that holds on expressions that contain
a variable.
Inductive has_var : expr -> Prop :=
| hv_var :
forall s, has_var (Evar s)
| hv_add_l :
forall e1 e2,
has_var e1 ->
has_var (Eadd e1 e2)
| hv_add_r :
forall e1 e2,
has_var e2 ->
has_var (Eadd e1 e2)
| hv_mul_l :
forall e1 e2,
has_var e1 ->
has_var (Emul e1 e2)
| hv_mul_r :
forall e1 e2,
has_var e2 ->
has_var (Emul e1 e2)
| hv_cmp_l :
forall e1 e2,
has_var e1 ->
has_var (Elte e1 e2)
| hv_cmp_r :
forall e1 e2,
has_var e2 ->
has_var (Elte e1 e2).
| hv_var :
forall s, has_var (Evar s)
| hv_add_l :
forall e1 e2,
has_var e1 ->
has_var (Eadd e1 e2)
| hv_add_r :
forall e1 e2,
has_var e2 ->
has_var (Eadd e1 e2)
| hv_mul_l :
forall e1 e2,
has_var e1 ->
has_var (Emul e1 e2)
| hv_mul_r :
forall e1 e2,
has_var e2 ->
has_var (Emul e1 e2)
| hv_cmp_l :
forall e1 e2,
has_var e1 ->
has_var (Elte e1 e2)
| hv_cmp_r :
forall e1 e2,
has_var e2 ->
has_var (Elte e1 e2).
We can also write boolean functions
that check the same properties.
Note that orb is disjuction over
booleans:
Print orb.
Fixpoint hasConst (e: expr) : bool :=
match e with
| Eint _ => true
| Evar _ => false
| Eadd e1 e2 => orb (hasConst e1) (hasConst e2)
| Emul e1 e2 => orb (hasConst e1) (hasConst e2)
| Elte e1 e2 => orb (hasConst e1) (hasConst e2)
end.
Fixpoint hasConst (e: expr) : bool :=
match e with
| Eint _ => true
| Evar _ => false
| Eadd e1 e2 => orb (hasConst e1) (hasConst e2)
| Emul e1 e2 => orb (hasConst e1) (hasConst e2)
| Elte e1 e2 => orb (hasConst e1) (hasConst e2)
end.
We can write that a little more compactly using
the "||" notation for orb provided by
the Bool library.
Fixpoint hasVar (e: expr) : bool :=
match e with
| Eint _ => false
| Evar _ => true
| Eadd e1 e2 => hasVar e1 || hasVar e2
| Emul e1 e2 => hasVar e1 || hasVar e2
| Elte e1 e2 => hasVar e1 || hasVar e2
end.
match e with
| Eint _ => false
| Evar _ => true
| Eadd e1 e2 => hasVar e1 || hasVar e2
| Emul e1 e2 => hasVar e1 || hasVar e2
| Elte e1 e2 => hasVar e1 || hasVar e2
end.
That looks way easier!
However, as the quarter progresses,
we'll see that sometime defining a
property as an inductive relation
is more convenient.
We can prove that our relational
and functional versions agree.
This shows that the hasConst function is COMPLETE
with respect to the relation has_const.
Thus, anything that satisfies the relation evaluates
to "true" under the function hasConst.
Lemma has_const_hasConst:
forall e,
has_const e ->
hasConst e = true.
Proof.
intros.
induction e.
+ simpl. reflexivity.
+ simpl.
forall e,
has_const e ->
hasConst e = true.
Proof.
intros.
induction e.
+ simpl. reflexivity.
+ simpl.
uh oh, trying to prove something false! it's OK though because we have a bogus hyp!
inversion H.
inversion lets us do case analysis on
how a hypothesis of an inductive type
may have been built. In this case, there
is no way to build a value of type
"has_const (Var s)", so we complete
the proof of this subgoal for all
zero ways of building such a value
+
here we use inversion to consider
how a value of type "has_const (Add e1 e2)"
could have been built
inversion H.
-
-
built with hc_add_l
subst.
subst rewrites all equalities it can
apply IHe1 in H1.
simpl.
simpl.
remember notation "||" is same as orb
rewrite H1. simpl. reflexivity.
-
-
built with hc_add_r
subst. apply IHe2 in H1.
simpl. rewrite H1.
simpl. rewrite H1.
use fact that orb is commutative
you can find this by turning on
auto completion or using a search query
Mul case is similar
inversion H; simpl; subst.
- apply IHe1 in H1; rewrite H1; auto.
- apply IHe2 in H1; rewrite H1;
rewrite orb_comm; auto.
+
- apply IHe1 in H1; rewrite H1; auto.
- apply IHe2 in H1; rewrite H1;
rewrite orb_comm; auto.
+
Lte case is similar
inversion H; simpl; subst.
- apply IHe1 in H1; rewrite H1; auto.
- apply IHe2 in H1; rewrite H1;
rewrite orb_comm; auto.
Qed.
- apply IHe1 in H1; rewrite H1; auto.
- apply IHe2 in H1; rewrite H1;
rewrite orb_comm; auto.
Qed.
Now for the other direction.
Here we'll prove that the hasConst function
is SOUND with respect to the relation.
That is, if hasConst produces true,
then there is some proof of the inductive
relation has_const.
Lemma hasConst_has_const:
forall e,
hasConst e = true ->
has_const e.
Proof.
intros.
induction e.
+ simpl.
forall e,
hasConst e = true ->
has_const e.
Proof.
intros.
induction e.
+ simpl.
we can prove this case with a constructor
constructor.
this uses hc_const
+
Uh oh, no constructor for has_const
can possibly produce a value of our
goal type! It's OK though because
we have a bogus hypothesis.
simpl in H.
discriminate.
+
discriminate.
+
now do Add case
simpl in H.
either e1 or e2 had a Const
consider cases for H
destruct H.
-
-
e1 had a Const
e2 had a Const
Mul case is similar
constructor will just use hc_mul_l
constructor. apply IHe1. assumption.
-
-
constructor will screw up and try hc_mul_l again! constructor is rather dim
constructor.
OOPS!
Lte case is similar
we can stitch these two lemmas together
- >
<-
Notice all that work was only for the "true" cases!
We can prove analogous facts for the "false" cases too.
Here we will prove the "false" cases directly.
However, note that you could use has_const_iff_hasConst
to get a much simpler proof.
Lemma not_has_const_hasConst:
forall e,
~ has_const e ->
hasConst e = false.
Proof.
unfold not. intros.
induction e.
+ simpl.
uh oh, trying to prove something bogus better exploit a bogus hypothesis
exfalso.
proof by contradiction
prove conjunction by proving left and right
split.
- apply IHe1. intro.
apply H. apply hc_add_l. assumption.
- apply IHe2. intro.
apply H. apply hc_add_r. assumption.
+
- apply IHe1. intro.
apply H. apply hc_add_l. assumption.
- apply IHe2. intro.
apply H. apply hc_add_r. assumption.
+
Mul case is similar
simpl; apply orb_false_iff.
split.
- apply IHe1; intro.
apply H. apply hc_mul_l. assumption.
- apply IHe2; intro.
apply H. apply hc_mul_r. assumption.
+
split.
- apply IHe1; intro.
apply H. apply hc_mul_l. assumption.
- apply IHe2; intro.
apply H. apply hc_mul_r. assumption.
+
Lte case is similar
simpl; apply orb_false_iff.
split.
- apply IHe1; intro.
apply H. apply hc_cmp_l. assumption.
- apply IHe2; intro.
apply H. apply hc_cmp_r. assumption.
Qed.
split.
- apply IHe1; intro.
apply H. apply hc_cmp_l. assumption.
- apply IHe2; intro.
apply H. apply hc_cmp_r. assumption.
Qed.
Here is a more direct proof based on the
iff we proved for the true case.
do case analysis on hasConst e eqn:? remembers the result in a hypothesis
now we have hasConst e = true in our hypothesis
We have a contradiction in our hypotheses discriminate won't work this time though
For the other case, this is easy
Qed.
Now the other direction of the false case
Lemma false_hasConst_hasConst:
forall e,
hasConst e = false ->
~ has_const e.
Proof.
unfold not. intros.
induction e;
forall e,
hasConst e = false ->
~ has_const e.
Proof.
unfold not. intros.
induction e;
crunch down everything in subgoals
get both proofs out of a conjunction
by destructing it
destruct H.
case analysis on H0 DISCUSS: how do we know to do this?
inversion H0.
- subst. auto.
- subst. auto.
auto will chain things for us
- subst. auto.
+
+
Mul case similar
Lte case similar
Since we've proven the iff for the true case We can use it to prove the false case This is the same lemma as above, but using our previous results
~ X is just X -> False
We can also do all the same
sorts of proofs for has_var and hasVar
TODO: try this without copying from above
TODO: try this without copying from above
TODO: try this without copying from above
Admitted.
we can also prove things about expressions
prove left side of disjunction
left.
constructor.
+
constructor.
+
prove right side of disjunction
right.
constructor.
+
constructor.
+
case analysis on IHe1
destruct IHe1.
- left. constructor. assumption.
- right. constructor. assumption.
+
- left. constructor. assumption.
- right. constructor. assumption.
+
Mul case similar
destruct IHe1.
- left. constructor. assumption.
- right. constructor. assumption.
+
- left. constructor. assumption.
- right. constructor. assumption.
+
Cmp case similar
destruct IHe1.
- left. constructor. assumption.
- right. constructor. assumption.
Qed.
- left. constructor. assumption.
- right. constructor. assumption.
Qed.
we could have gotten some of the
has_const lemmas by being a little clever!
(but then we wouldn't have
learned as many tactics ;) )
Lemma has_const_hasConst':
forall e,
has_const e ->
hasConst e = true.
Proof.
intros.
induction H; simpl; auto.
+ rewrite orb_true_iff. auto.
+ rewrite orb_true_iff. auto.
+ rewrite orb_true_iff. auto.
+ rewrite orb_true_iff. auto.
+ rewrite orb_true_iff. auto.
+ rewrite orb_true_iff. auto.
Qed.
or even better
Lemma has_const_hasConst'':
forall e,
has_const e ->
hasConst e = true.
Proof.
intros.
induction H; simpl; auto;
rewrite orb_true_iff; auto.
Qed.
Lemma not_has_const_hasConst'':
forall e,
~ has_const e ->
hasConst e = false.
Proof.
unfold not; intros.
destruct (hasConst e) eqn:?.
- exfalso. apply H.
apply hasConst_has_const; auto.
- reflexivity.
Qed.
Lemma false_hasConst_hasConst'':
forall e,
hasConst e = false ->
~ has_const e.
Proof.
unfold not; intros.
destruct (hasConst e) eqn:?.
- discriminate.
- rewrite has_const_hasConst in Heqb.
forall e,
has_const e ->
hasConst e = true.
Proof.
intros.
induction H; simpl; auto;
rewrite orb_true_iff; auto.
Qed.
Lemma not_has_const_hasConst'':
forall e,
~ has_const e ->
hasConst e = false.
Proof.
unfold not; intros.
destruct (hasConst e) eqn:?.
- exfalso. apply H.
apply hasConst_has_const; auto.
- reflexivity.
Qed.
Lemma false_hasConst_hasConst'':
forall e,
hasConst e = false ->
~ has_const e.
Proof.
unfold not; intros.
destruct (hasConst e) eqn:?.
- discriminate.
- rewrite has_const_hasConst in Heqb.
NOTE: we got another subgoal!
* discriminate.
* assumption.
Qed.
* assumption.
Qed.
In general:
Relational defns are nice when you want to use inversion.
Functional defns are nice when you want to use simpl.
This page has been generated by coqdoc