Library ex6

Blatt 6 —- Properties and Specifications

SemProg SoSe 2013, Set 6

Based on the material of the online course "Software Foundations" by Pierce et al. Submit your solutions to tadeusz.m.litak at cs.fau.de
Please submit by Jun 9, anywhere on earth.
However, you are strongly encouraged to do the exercise before the Wed lecture, even if additional bonus points for this have been entirely abolished.
The file consists of 7 obligatory exercises, yielding 45 points altogether and one bonus exercise, which yields 10 additional points for those willing.

Require Export Logic.


Exercise block: decidable equality

Our previous exercise Blatt hopefully drove home the message that the law of excluded middle and other classical tautologies are not available for free in Coq. However, for equality propositions the situation often looks better. Here is how we can define types with decidable equality in Coq.

Definition DecidEquality (X: Type) := forall x y : X, (x = y) \/ ~(x = y).

From earlier chapters we also remember boolean equality for natural numbers beq_nat. Let us say that a type has boolean equality in such a situation.

Definition BoolEquality (X: Type) (f: X -> X -> bool) :=
           forall x y, if (f x y) then (x = y) else ~(x = y).

Boolean equality should naturally imply decidable equality. Furthermore, our good friends like booleans and natsallow obvious boolean equality functions. This is an easy warm-up exercise.

Exercise 1: 5 points (natBooEq, booBooEq, Bool_Decid)


Theorem natBooEq: BoolEquality nat beq_nat.
Proof. Admitted.

Definition beq_bool (b c: bool) : bool := andb (orb (negb b) c) (orb (negb c) b).

Theorem booBooEq: BoolEquality bool beq_bool.
Proof. Admitted.

Theorem Bool_Decid: forall (X: Type) (f: X -> X -> bool), (BoolEquality X f) -> (DecidEquality X).
Proof. Admitted.

One would think that the converse implication will also be a piece of cake, i.e., decidable equality should yield a straightforward equality function:
Definition booleq_wrong (XType) (hDecidEquality X) : X -> X -> bool :=
fun x y  => match (h x ywith
                | or_introl _ => true
                | or_intror _ => false
   end.
Try to uncomment this definition. What is the error message that you get? Why?

Exercise 2: 7.5 point (listDecEq, listBooEq)

Types with boolean/decidable equality can be used to construct new ones. The most obvious way of doing so is provided by lists.

Theorem listDecEq: forall X, DecidEquality X -> DecidEquality (list X).
Proof.
Admitted.

To do the same with boolean equality, we first need to define a helper function.

Fixpoint lliftb (X: Type) (f: X -> X -> bool) (l1 l2: list X) : bool := admit.
<- FILL THIS IN SO THAT YOU CAN PROVE

Theorem listBooEq: forall X f, (BoolEquality X f) -> BoolEquality (list X) (lliftb X f).
Proof. Admitted.

How about other types? Can you think of any candidates for boolean/decidable equality for:
  • functions nat -> nat
  • real numbers (however you want to implement them in Coq)
  • Prop itself?

Exercise block: specifications for lists

Exercise 3: 5 points (good_subseq_extend, maximal_strengthening)

For a warm-up, let us finish what we began at the beginning of PropRelInd lecture. Here are our definitions again.

Inductive subseq' {X:Type} : list X -> list X -> Prop :=
  | sub_nil : forall l, subseq' [] l
  | sub_take : forall x l1 l2, subseq' l1 l2 -> subseq' (x :: l1) (x :: l2)
  | sub_skip : forall x l1 l2, subseq' l1 l2 -> subseq' l1 (x :: l2).

Definition maximal {X:Type} (lmax : list X) (P : list X -> Prop) := P
lmax /\ forall l', P l' -> length l' <= length lmax.

A "good subsequence" for a given list l and a test is a subsequence of l all of whose members evaluate to true under the test.

Inductive all (X : Type) (P : X -> Prop) : list X -> Prop :=
| all_nil : all X P []
| all_cons : forall x l, P x -> all X P l -> all X P (x::l).

Fixpoint forallb {X : Type} (test : X -> bool) (l : list X) : bool :=
  match l with
    | [] => true
    | x :: l' => andb (test x) (forallb test l')
  end.

Definition good_subseq {X:Type} (test : X -> bool) (l lsub : list X) :=
   subseq' lsub l /\ forallb test lsub = true.

Why, prove now the results we just stated w/o proof during the lecture...

Theorem good_subseq_extend : forall (X:Type) (test : X -> bool)
                                  (l lsub : list X) (x : X),
  good_subseq test l lsub ->
  test x = true ->
  good_subseq test (x::l) (x::lsub).
Proof. Admitted.

Theorem maximal_strengthening : forall (X:Type) (x:X)
                                     (lmax l : list X)
                                     (test : X -> bool),
  maximal lmax (good_subseq test (x::l)) ->
  test x = false ->
  maximal lmax (good_subseq test l).
Proof. Admitted.

Exercise 4: 7.5 points (forallb_matches_spec)

Let us try something more interesting. We can write down a specification for forallb using property all. Your job is to prove it works.

Theorem forallb_matches_spec {X : Type} : forall (test : X -> bool), forall (l : list X),
if (forallb test l) then (all X (fun x => test x = true) l) else exists x : X, exists l1, exists l2, (test x = false) /\ l = l1 ++ x::l2.
Proof. Admitted.

Exercise 5: 7.5 points (repeatslog_equivalent_repeatsind)

The following inductively defined proposition...

Inductive appears_in {X:Type} (a:X) : list X -> Prop :=
  | ai_here : forall l, appears_in a (a::l)
  | ai_later : forall b l, appears_in a l -> appears_in a (b::l).

...gives us a precise way of saying that a value a appears at least once as a member of a list l.
Here are a few warm-ups about appears_in.

Lemma appears_in_app : forall {X:Type} (xs ys : list X) (x:X),
     appears_in x (xs ++ ys) -> appears_in x xs \/ appears_in x ys.
Proof. Admitted.

Lemma app_nil : forall {X:Type} (xs: list X), xs ++ [] = xs.
Proof. Admitted.

Lemma app_appears_in_left : forall {X:Type} (xs ys : list X) (x:X),
     appears_in x xs -> appears_in x (xs ++ ys).
Proof. Admitted.

Lemma app_appears_in_right : forall {X:Type} (xs ys : list X) (x:X),
     appears_in x ys -> appears_in x (xs ++ ys).
Proof. Admitted.

Lemma app_appears_in : forall {X:Type} (xs ys : list X) (x:X),
     appears_in x xs \/ appears_in x ys -> appears_in x (xs ++ ys).
Proof. Admitted.

We can use this property to definite inductively what it means that a list contains repeated occurrences of some element(s).

Inductive repeatsind (X:Type) : list X -> Prop :=
  | rep_here : forall l x, (appears_in x l) -> repeatsind X (x :: l)
  | rep_furth : forall l x, repeatsind X l -> repeatsind X (x::l).

However, there is an obvious alternative definition...

Definition repeatslog (X:Type) (l : list X) : Prop :=
 exists x, exists l1, exists l2, exists l3, l = l1 ++ [x] ++ l2 ++ [x] ++ l3.

In fact, this definition is a good excuse to define our first nontrivial tactical. Do you understand what it does?

Tactic Notation "repeatslog" ident(hip) ident(x) ident(l1) ident(l2) ident(l3) ident(Hl3) :=
          let Hx := fresh in inversion hip as [x Hx]; clear hip;
          let Hl1 := fresh in inversion Hx as [l1 Hl1]; clear Hx; let Hl2 := fresh in inversion Hl1 as [l2 Hl2]; clear Hl1; inversion Hl2 as [l3 Hl3]; clear Hl2.

You will most likely find it quite helpful in the proof that the two definitions are equivalent:

Theorem repeatslog_equivalent_repeatsind {X: Type} : forall l, repeatslog X l <-> repeatsind X l.
Proof. Admitted.

As this is an equivalence, you may of course prove first each of directions in separate lemmas.

Evaluating and optimizing polymorphic string expressions.

Exercise 6: 7.5 points (optimizetrim_sound)

Here is a definition of abstract syntax trees for (polymorphic) string expressions.

Inductive sexp (X : Type) : Type :=
  | SNil : sexp X
  | SSing : X -> sexp X
  | SConc : sexp X -> sexp X -> sexp X
  | STrimF : sexp X -> sexp X.
removal of the first char of the string, i.e., "trimming" the front
Define functions evaluting these string expressions to lists.

Fixpoint seval {X : Type} (s: sexp X) : list X := admit.
<- FILL THIS IN! See many examples below if you want to double-check how exactly this function should be defined.
Of course, there is a lot of redundancy in our expressions. Here is a function which tries to remove at least some. Do you understand what it does?

Fixpoint optimizetrim {X : Type} (s: sexp X) : sexp X :=
match s with
  | SNil => SNil X
  | SSing x => SSing X x
  | SConc s' t => SConc X (optimizetrim s') (optimizetrim t)
  | STrimF (SConc SNil t) => STrimF X (optimizetrim t)
  | STrimF (SConc (SSing x) t) => (optimizetrim t)
  | STrimF t => STrimF X (optimizetrim t)
end.

Just to double-check we understand what is going on there... a few examples with the intended output.

Definition testexp := SConc nat (SConc nat (SNil nat) (SConc nat (SSing nat 1) (SSing nat 2))) (SSing nat 1).
Definition trimtexp := (STrimF nat (STrimF nat testexp)).

Eval compute in seval(testexp).
[ [1, 2, 1] : list nat ]

Eval compute in seval(trimtexp).
  = [1]  : list nat

Eval compute in optimizetrim(trimtexp).
 = STrimF nat
         (STrimF nat
            (SConc nat
               (SConc nat (SNil nat) (SConc nat (SSing nat 1) (SSing nat 2)))
               (SSing nat 1)))
     : sexp nat

Definition testexp' := SConc nat (SSing nat 1) (SConc nat (SNil nat) (SConc nat (SSing nat 1) (SSing nat 2))) .
Definition trimtexp' := (STrimF nat (STrimF nat testexp')).

Eval compute in seval(testexp').
 = [1, 1, 2]
     : list nat

Eval compute in seval(trimtexp').
     = [2]
     : list nat
Eval compute in optimizetrim(trimtexp').
     = STrimF nat
         (SConc nat (SNil nat) (SConc nat (SSing nat 1) (SSing nat 2)))
     : sexp nat

Eval compute in optimizetrim(optimizetrim(trimtexp')).
      = STrimF nat (SConc nat (SSing nat 1) (SSing nat 2))
     : sexp nat

Eval compute in optimizetrim(optimizetrim(optimizetrim(trimtexp'))).
     = SSing nat 2
     : sexp nat

Tactic Notation "sexp_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "SNil" | Case_aux c "SSing"
  | Case_aux c "SConc" | Case_aux c "STrimF" ].

Now we can prove a theorem that our optimization is indeed correct:

Theorem optimizetrim_sound : forall (X: Type) (s: sexp X),
  seval (optimizetrim s) = seval s.
Proof. Admitted.

Of course, tacticals can be used to make the proof this theorem much shorter and you are well advised to try some mentioned at the end of the Wed lecture. With a clever use of these, you should not need more than 6-7 lines... okay, maybe a bit longish lines.

Explicit Proof Objects for Induction and Improved Proofs

Exercise 7: 5 points (gorgeous_is_trivial')

This bit has not been explained in too much detail during the PropRelInd lecture... Hopefully the exercise will make up for it.
Although tactic-based proofs are normally much easier to work with, the ability to write a proof term directly is sometimes very handy, particularly when we want Coq to do something slightly non-standard.
Recall the induction principle on naturals that Coq generates for us automatically from the Inductive declation for nat.


There's nothing magic about this induction lemma: it's just another Coq lemma that requires a proof. Coq generates the proof automatically too...

Print nat_ind.

We can read this as follows: Suppose we have evidence f that P holds on 0, and evidence f0 that forall n:nat, P n -> P (S n). Then we can prove that P holds of an arbitrary nat n via a recursive function F (here defined using the expression form Fix rather than by a top-level Fixpoint declaration). F pattern matches on n:
  • If it finds 0, F uses f to show that P n holds.
  • If it finds S n0, F applies itself recursively on n0 to obtain evidence that P n0 holds; then it applies f0 on that evidence to show that P (S n) holds.
F is just an ordinary recursive function that happens to operate on evidence in Prop rather than on terms in Set.
Those interested in functional programming may notice that the match in F requires an annotation as n0 return (P n0) to help Coq's typechecker realize that the two arms of the match actually return the same type (namely P n). This is essentially like matching over a GADT (generalized algebraic datatype) in Haskell. In fact, F has a dependent type: its result type depends on its argument; GADT's can be used to describe simple dependent types like this.
We can adapt this approach to proving nat_ind to help prove non-standard induction principles too. Recall our desire to prove that
forall n : nat, even n -> ev n.
Attempts to do this by standard induction on n fail, because the induction principle only lets us proceed when we can prove that even n -> even (S n) — which is of course never provable. What we did earlier in this chapter was a bit of a hack:
Theorem even__ev : forall n : nat, (even n -> ev n) /\ (even (S n) -> ev (S n)).
We can make a much better proof by defining and proving a non-standard induction principle that goes "by twos":

 Definition nat_ind2 :
    forall (P : nat -> Prop),
    P 0 ->
    P 1 ->
    (forall n : nat, P n -> P (S(S n))) ->
    forall n : nat , P n :=
       fun P => fun P0 => fun P1 => fun PSS =>
          fix f (n:nat) := match n return P n with
                             0 => P0
                           | 1 => P1
                           | S (S n') => PSS n' (f n')
                          end.

Once you get the hang of it, it is entirely straightforward to give an explicit proof term for induction principles like this. Proving this as a lemma using tactics is much less intuitive (try it!).
The induction ... using tactic gives a convenient way to specify a non-standard induction principle like this.

Lemma even__ev' : forall n, even n -> ev n.
Proof.
 intros.
 induction n as [ | |n'] using nat_ind2.
  Case "even 0".
    apply ev_0.
  Case "even 1".
    inversion H.
  Case "even (S(S n'))".
    apply ev_SS.
    apply IHn'. unfold even. unfold even in H. simpl in H. apply H.
Qed.

Now, recall that in the previous Blatt we used a similar hack to show near-triviality of gorgeousness. Naturally, we can then hope to fix it in a similar way: defining an induction principle that goes "by three".

Definition nat_ind3 :
    forall (P : nat -> Prop),
    P 0 ->
    P 1 ->
    P 2 ->
    (forall n : nat, P n -> P (S(S(S n)))) ->
    forall n : nat , P n := admit.
<- FILL THIS IN!
Prove a similarly improved theorem using the new induction principle.

Theorem gorgeous_is_trivial' : forall n, gorgeous (n + 8).
Proof. Admitted.

This is the end of obligatory exercises.

BONUSAUFGABE (10 points)

Still not fed up with proving properties and specifications of lists? Well, here is something more.
Use appears_in to define an inductive proposition no_repeats X l, which should be provable exactly when l is a list (with elements of type X) where every member is different from every other. For example, no_repeats nat [1,2,3,4] and no_repeats bool [] should be provable, while no_repeats nat [1,2,1] and no_repeats bool [true,true] should not be.

Inductive no_repeats (X: Type) : list X -> Prop := .
<- Fill this in!

Check no_repeats_ind.

Eval compute in (no_repeats nat [1,2,3,4]).

Here is one more natural notion:

Definition disjoint {X:Type} (l1 l2: list X) := forall (x:X), appears_in x l1 -> ~ appears_in x l2.

Now prove the following theorem relating disjoint, no_repeats and ++.

Theorem disjoint_no_repeats (X : Type) : forall l1 l2, (no_repeats X l1 /\ no_repeats X l2 /\ disjoint l1 l2) <-> no_repeats X (l1 ++ l2).
Proof. Admitted.