Lecture 02

Infer some type arguments automatically.
Set Implicit Arguments.

Note that the type constructor for functions (arrow "->") associates to the right:
    A -> B -> C = A -> (B -> C)

Inductive list (A: Type) : Type :=
| nil : list A
| cons : A -> list A -> list A.

Fixpoint length (A: Type) (l: list A) : nat :=
  match l with
  | nil _ => O
  | cons x xs => S (length xs)
  end.

So far, Coq will not infer the type argument for nil:
Check (cons 1 nil).

Error: The term "nil" has type "forall A : Type, list A"
 while it is expected to have type "list nat".

Check (cons 1 (nil nat)).

We can tell Coq to always try though:
Arguments nil {A}.

Check (cons 1 nil).

countdown is a useful function for testing. countdown n produces the list:
  n :: (n - 1) :: (n - 2) :: .. :: 1 :: 0 :: nil
Fixpoint countdown (n: nat) :=
  match n with
  | O => cons n nil
  | S m => cons n (countdown m)
  end.

We can run our countdown function on some example inputs:
Eval cbv in (countdown 0).
     = cons 0 nil
     : list nat
Eval cbv in (countdown 3).
     = cons 3 (cons 2 (cons 1 (cons 0 nil)))
     : list nat
Eval cbv in (countdown 10).
     = cons 10
       (cons 9
        (cons 8
         (cons 7
          (cons 6
           (cons 5 
            (cons 4
             (cons 3
              (cons 2
               (cons 1
                (cons 0 nil))))))))))
     : list nat
map takes a function f and list l and produces a new list by applying f to each element of l.
(Note that because Gallina is a pure functional programming language, the original input list is completely unchanged.)
Fixpoint map (A B: Type) (f: A -> B) (l: list A) : list B :=
  match l with
  | nil => nil
  | cons x xs => cons (f x) (map f xs)
  end.

Eval cbv in (map (plus 1) (countdown 3)).
     = cons 4 (cons 3 (cons 2 (cons 1 nil)))
     : list nat
Eval cbv in (map (fun _ => true) (countdown 3)).
     = cons true (cons true (cons true (cons true nil)))
     : list bool

Definition is_zero (n: nat) : bool :=
  match n with
  | O => true
  | S m => false
  end.

Eval cbv in (map is_zero (countdown 3)).
     = cons false (cons false (cons false (cons true nil)))
     : list bool

Fixpoint is_even (n: nat) : bool :=
  match n with
  | O => true
  | S O => false
  | S (S m) => is_even m
  end.

Eval cbv in (map is_even (countdown 3)).
     = cons false (cons true (cons false (cons true nil)))
     : list bool
map produces an output list which is exactly the same length as its input list.
(Note that this proof uses bullets (+). See the course web page for more info.)
Lemma map_length :
  forall (A B : Type) (f : A -> B) (l : list A),
  length (map f l) = length l.
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl.
Replace "length (map f l)" with "length l"
    rewrite IHl.
    reflexivity.
Qed.

To prove properties about all elements of a type, we typically use induction.
We do this by proving that the property holds on the "base cases", that is, for nonrecursive constructors.
For example, (O : nat) and (nil : list A) are base cases for nat and list respectively.
Then, we prove that the property is preserved by each of the recursive constructors, assuming it holds for all the recursive arguments to that constructor.
To prove the inductive case for a property P of nats, we need to prove
    forall n, P n -> P (S n)
For lists, we need to prove
    forall l x, P l -> P (cons x l)
Function composition, like from math class.
Definition compose
  (A B C : Type)
  (f : B -> C)
  (g : A -> B)
  : A -> C :=
  fun x => f (g x).

Mapping two functions one after the other over a list is the same as just mapping their composition over the list.
Lemma map_map_compose:
  forall (A B C : Type)
         (g : A -> B) (f : B -> C) (l : list A),
  map f (map g l) = map (compose f g) l.
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl. rewrite IHl.
need to "unfold" compose so simpl can grind
    unfold compose. reflexivity.
Qed.

Often we'd like to process a list by "crunching" it down into a single value.
foldr does this by taking a function f, a list cons e1 (cons e2 (cons e3 ... (cons eN nil) ...)), and an initial "accumulator" or "state" b and computing:
  f e1 (f e2 (f e3 ... (f eN b) ...))
Fixpoint foldr (A B : Type) (f : A -> B -> B)
               (l : list A) (b : B) : B :=
  match l with
  | nil => b
  | cons x xs => f x (foldr f xs b)
  end.

Again, foldr works by putting a function f in for each cons:
  foldr f (cons 1 (cons 2 (cons 3 nil))) x
  -->
  f 1 (f 2 (f 3 x))
See how foldr replaces cons with f and nil with x.
foldr plus sums a list of nats. Let's sum the values from 0 to 10
Eval cbv in (foldr plus (countdown 10) 0).
     = 55
     : nat
Consider our good friend the factorial function:
Fixpoint fact (n: nat) : nat :=
  match n with
  | O => 1
  | S m => mult n (fact m)
  end.

Eval cbv in (fact 0).
Eval cbv in (fact 1).
Eval cbv in (fact 2).
Eval cbv in (fact 3).
Eval cbv in (fact 4).

We can write it slightly differently using foldr:
Definition fact' (n: nat) : nat :=
  match n with
  | O => 1
  | S m => foldr mult (map (plus 1) (countdown m)) 1
  end.

Eval cbv in (fact' 0).
Eval cbv in (fact' 1).
Eval cbv in (fact' 2).
Eval cbv in (fact' 3).
Eval cbv in (fact' 4).

As an exercise, please prove these two versions of factorial equivalent:
Lemma fact_fact':
  forall n,
  fact n = fact' n.
Proof.
challenge problem
Admitted.

It turns out we can write many list function just in terms of foldr. Here's a definition of map using foldr:
Definition map' (A B : Type)
  (f : A -> B) (l : list A) : list B :=
  foldr (fun x acc => cons (f x) acc) l nil.

We can prove our "foldr" version of map equivalent to the direct definition:
Lemma map_map' :
  forall (A B : Type) (f : A -> B) (l : list A),
  map f l = map' f l.
Proof.
  intros.
  induction l.
  + simpl. unfold map'. simpl. reflexivity.
  + simpl. rewrite IHl.
again, need to unfold so simpl can grind
    unfold map'. simpl.
    reflexivity.
Note: this proof is very sensitive to the order of rewrite and unfold!
Qed.

We can also define another flavor of fold, called foldl, that starts applying f to the first element of the list instead of the last.
What's the difference? When would you use foldl instead of foldr?
Fixpoint foldl (A B : Type)
  (f : A -> B -> B)
  (l : list A) (b : B) : B :=
  match l with
  | nil => b
  | cons x xs => foldl f xs (f x b)
  end.

When working with lists, appending one list onto the end of another is very useful. This app function does exactly that. Notice how similar it is to adding nats.
Fixpoint app (A : Type)
 (l1 : list A) (l2 : list A) : list A :=
  match l1 with
  | nil => l2
  | cons x xs => cons x (app xs l2)
  end.

Eval cbv in (app (cons 1 (cons 2 nil)) (cons 3 nil)).
     = cons 1 (cons 2 (cons 3 nil))
     : list nat
This is the analog of our lemma about (n + 0) from Lecture 01, but for appending nil onto a list.
Notice how similar the proofs are! We have seen this pattern several times already.
Theorem app_nil:
  forall A (l: list A),
  app l nil = l.
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl. rewrite IHl. reflexivity.
Qed.

app is associative, meaning we can freely re-associate (move parens around).
There's the same proof pattern again!
Theorem app_assoc:
  forall A (l1 l2 l3: list A),
  app (app l1 l2) l3 = app l1 (app l2 l3).
Proof.
  intros.
  induction l1.
  + simpl. reflexivity.
  + simpl. rewrite IHl1. reflexivity.
Qed.

Sometimes a list is "backward" from the order we would prefer it in.
Here is a simple but "inefficient" way to reverse a list.
Fixpoint rev (A: Type) (l: list A) : list A :=
  match l with
  | nil => nil
  | cons x xs => app (rev xs) (cons x nil)
  end.

We say that the version of rev above is "inefficient" because it is not tail recursive. A function is tail recursive when all recursive calls are the final action of the function. You can read more about tail recursion here: https://en.wikipedia.org/wiki/Tail_call
Tail recursion is generally faster and leads to less stack space consumption, but it is more complicated and therefore often trickier to reason about.
Below we define a tail recursive function to reverse a list. We first define a helper function fast_rev_aux which takes an additional argument acc ("acc" is short for "accumulator"). We "accumulate" the resulting reversed list with each recursive call.
Note how fast_rev_aux only calls itself in tail position, i.e., as its result.
Tail recursion is typically faster because compilers for functional programming languages often perform tail-call optimization ("TCO"), in which stack frames are re-used by recursive calls.
Fixpoint fast_rev_aux (A : Type)
  (l : list A) (acc : list A) : list A :=
  match l with
  | nil => acc
  | cons x xs => fast_rev_aux xs (cons x acc)
  end.

Definition fast_rev (A : Type)
  (l : list A) : list A :=
  fast_rev_aux l nil.

We can make sure our faster, tail-recursive version of reverse is right by proving it equivalent to the simpler, non-tail-recursive version.
However, as we see below, we will not be able to do this directly. We will first need to prove a helper lemma with a stronger induction hypothesis.
Theorem rev_ok:
  forall A (l : list A),
  fast_rev l = rev l.
Proof.
  intros.
  induction l.
  + simpl.
reduces rev, but does nothing to rev_fast
    unfold fast_rev.
unfold fast_rev to fast_rev_aux
    simpl.
now we can simplify the term
    reflexivity.
TIP: if simpl doesn't work, try unfolding!
  + unfold fast_rev in *.
this looks like it could be trouble...
    simpl. rewrite <- IHl.
STUCK! need to know about the rev_aux accumulator (acc) TIP: if your IH seems weak, try proving something more general
Abort.

Lemma fast_rev_aux_ok:
  forall A (l1 l2 : list A),
  fast_rev_aux l1 l2 = app (rev l1) l2.
Proof.
  intros.
  induction l1.
  + simpl. reflexivity.
  + simpl.
STUCK AGAIN! need to know for *any* l2 TIP: if your IH seems weak, only intro up to the induction variable
Abort.

Lemma fast_rev_aux_ok:
  forall A (l1 l2: list A),
  fast_rev_aux l1 l2 = app (rev l1) l2.
Proof.
  intros A l1.
  induction l1.
  + intros. simpl. reflexivity.
  +
Compare the induction hypothesis (IHl1) here with the one we had before. What's different? Why is this called "generalizing" the induction hypothesis?
    intros. simpl.
    rename l2 into foo.
Note that we can rewrite by IHl1 even though it is universally quantified (i.e., there's a forall). Coq will figure out what to replace l2 with in IHl1 (cons a foo).
    rewrite IHl1. rewrite app_assoc.
    simpl.
    reflexivity.
Qed.

With our stronger induction hypothesis from the lemma, we can now prove rev_ok as a special case of rev_aux_ok.
Lemma rev_ok:
  forall A (l: list A),
  fast_rev l = rev l.
Proof.
  intros.
  unfold fast_rev.
  rewrite fast_rev_aux_ok.
  rewrite app_nil.
  reflexivity.
Qed.

Here we'll stop for an in class exercise!


	                            ~-.
          ,,,;            ~-.~-.~-
         (.../           ~-.~-.~-.~-.~-.
         } o~`,         ~-.~-.~-.~-.~-.~-.
         (/    \      ~-.~-.~-.~-.~-.~-.~-.
          ;    \    ~-.~-.~-.~-.~-.~-.~-.
         ;     {_.~-.~-.~-.~-.~-.~-.~
        ;:  .-~`    ~-.~-.~-.~-.~-.
       ;.: :'    ._   ~-.~-.~-.~-.~-
        ;::`-.    '-._  ~-.~-.~-.~-
         ;::. `-.    '-,~-.~-.~-.
          ';::::.`''-.-'
            ';::;;:,:'
               '||"
               / |
             ~` ~"'

add an element to the end of a list
Fixpoint snoc (A : Type)
  (l : list A) (x : A) : list A :=
  match l with
  | nil => cons x nil
  | cons y ys => cons y (snoc ys x)
  end.

Theorem snoc_app_singleton :
  forall A (l : list A) (x : A),
  snoc l x = app l (cons x nil).
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl. rewrite IHl. reflexivity.
Qed.

Theorem app_snoc_l :
  forall A (l1 : list A) (l2 : list A) (x : A),
  app (snoc l1 x) l2 = app l1 (cons x l2).
Proof.
  intros.
  induction l1.
  + simpl. reflexivity.
  + simpl. rewrite IHl1. reflexivity.
Qed.

Theorem app_snoc_r :
  forall A (l1 : list A) (l2 : list A) (x : A),
  app l1 (snoc l2 x) = snoc (app l1 l2) x.
Proof.
  intros.
  induction l1.
  + simpl. reflexivity.
  + simpl. rewrite IHl1. reflexivity.
Qed.

Another simple but inefficient way to reverse a list
Fixpoint rev_snoc (A : Type) (l : list A) : list A :=
  match l with
  | nil => nil
  | cons x xs => snoc (rev_snoc xs) x
  end.

Lemma fast_rev_aux_ok_snoc:
  forall A (l1 l2 : list A),
  fast_rev_aux l1 l2 = app (rev_snoc l1) l2.
Proof.
  intros A l1.
  induction l1.
  + intros. simpl. reflexivity.
  + intros. simpl.
    rewrite IHl1.
    rewrite app_snoc_l.
    reflexivity.
Qed.

Lemma fast_rev_ok_snoc:
  forall A (l : list A),
  fast_rev l = rev_snoc l.
Proof.
  intros.
  unfold fast_rev.
  rewrite fast_rev_aux_ok_snoc.
  rewrite app_nil.
  reflexivity.
Qed.

We'll finish off with some example lemmas about rev and length.
Note how often the same proof pattern keeps emerging!

Lemma length_app:
  forall A (l1 l2 : list A),
  length (app l1 l2) = plus (length l1) (length l2).
Proof.
  intros.
  induction l1.
  + simpl. reflexivity.
  + simpl. rewrite IHl1. reflexivity.
Qed.

Lemma plus_1_S:
  forall n,
  plus n 1 = S n.
Proof.
  intros.
  induction n.
  + simpl. reflexivity.
  + simpl. rewrite IHn. reflexivity.
Qed.

Lemma rev_length:
  forall A (l: list A),
  length (rev l) = length l.
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl. rewrite length_app.
    simpl. rewrite plus_1_S.
    rewrite IHl. reflexivity.
Qed.

Lemma rev_app:
  forall A (l1 l2: list A),
  rev (app l1 l2) = app (rev l2) (rev l1).
Proof.
  intros.
  induction l1.
  + simpl. rewrite app_nil. reflexivity.
  + simpl. rewrite IHl1. rewrite app_assoc.
    reflexivity.
Qed.

Lemma rev_involutive:
  forall A (l: list A),
  rev (rev l) = l.
Proof.
  intros.
  induction l.
  + simpl. reflexivity.
  + simpl. rewrite rev_app.
    simpl. rewrite IHl. reflexivity.
Qed.

This page has been generated by coqdoc