Episode 03: Lists

This command asks Coq to infer ``easy'' type arguments.
Set Implicit Arguments.
As a result of running Set Implicit Arguments, sometimes we will apply functions or constructors to fewer arguments than they are defined to take. When you see that, it simply indicates that Coq was able to automatically infer the missing arguments.


A parameterized, recursive type:
Inductive llist (T: Type) :=
| lnil : llist T
| lcons : T -> llist T -> llist T.

Above we define list to be a type parameterized by some other type T. Furthermore, we say that there are exactly two ways to build a llist: (1) using the lnil constructor or (2) using the lcons constructor. When the lnil constructor is applied to a type T, it yields a value of type llist T:
Check lnil.
lnil
     : forall T : Type, llist T
We'll talk a lot more about forall throughout the course, but in this case you can think of it as just a fancy ->. When the lcons constructor is applied to a type T, a value of type T, and a value of type llist T, it yields a value of type llist T:
Check lcons.
lcons
     : forall T : Type, T -> llist T -> llist T
Coq will infer the type argument T for lcons, but it will not try to infer the type argument T for lnil, even if it is obvious from context.
Print llist.
So this will break, even though context forces T to be nat:
Check (lcons 1 (lcons 2 (lcons 3 lnil))).
But this works:
Check (lcons 1 (lcons 2 (lcons 3 (lnil nat)))).

However, we can tell Coq to always try to infer the type argument T of lnil from context:
Arguments lnil [T].
Print llist.
Check (lcons 1 (lcons 2 (lcons 3 lnil))).


The list type Coq provides is isomorphic to our llist.
Print list.

Get nifty notations for cons, append, etc.
Require Import List.

We'll use countdown to help test our list functions.
Fixpoint countdown (n: nat) :=
  match n with
    | O => nil
    | S m => n :: countdown m
  end.

Eval cbv in (countdown 5).

For polymorphic functions like length, we must explicitly add a type argument.
If we try to define length without the type argument, like so:
Fixpoint length l :=
  match l with
    | nil => 0
    | x::xs => S (length xs)
  end.
we will get an error like:
Error: Cannot infer an internal placeholder of type "Type".
To avoid this problem, just add the type argument and the type for l. Coq can infer the rest:
Fixpoint length (A: Type) (l: list A) :=
  match l with
    | nil => 0
    | x::xs => S (length xs)
  end.

Eval cbv in (length (1 :: 2 :: 3 :: nil)).
Eval cbv in (length (countdown 5)).
Eval cbv in (length (countdown 3)).
Eval cbv in (length (countdown 1)).

In lecture, we noticed a simple relationship between length and countdown:
Lemma length_countdown:
  forall n, length (countdown n) = n.
Proof.
Consider arbitrary n.
  intro.

We can prove property for arbitrary (S n) if we know property is true for n, so use induction.
  induction n.

Base case: n = O Need to prove length (countdown O) = O, which reflexivity solves by crunching down countdown 0 to nil, then length nil to O, which leaves the goal O = O.
  { reflexivity. }

  
Inductive case: n = S m Need to prove length (countdown (S m)) = S m. First we can cruch countdown (S m) down to S m :: countdown m. Now we crunch length (S m :: countdown m) down to S (length (countdown m)). This leaves us with the goal:
  S (length (countdown m)) = S m
Now if only we happened to know that length (countdown m) = m ... BUT WAIT! That's *exactly* what our induction hypothesis give us! After crunching with simpl, the firstorder tactic is smart enought to figure all this out and use the induction hypothesis to finish the proof.
  { simpl. firstorder. }
Qed.

Append one list to the end of another.
Fixpoint append (A: Type) (l1 l2: list A) :=
  match l1 with
    | nil => l2
    | x::xs => x :: append xs l2
  end.

Eval cbv in (append (countdown 5) (countdown 2)).

Show that length of two lists appended is just the sum of their lengths.
Lemma length_append:
  forall A (l1 l2: list A),
  length (l1 ++ l2) = length l1 + length l2.
Proof.
Consider arbitrary lists l1 and l2.
  intros.

We can prove for arbitrary l1 = x :: xs if we just know property is true for xs, so use induction.
  induction l1.

Base case: l1 = nil. Need to prove length (append nil l2) = length nil + length l2, which reflexivity solves by crunching down to length (l2) = O + length l2 and then to length l2 = length l2.
  { reflexivity. }

  
Inductive case: l1 = x :: xs Need to prove length (append (x :: xs) l2) = length (x :: xs) + length l2. First we can cruch length (append (x :: xs) l2) down to length (x :: append xs l2) and again to S (length (append xs l2)). We can also crunch length (x :: xs) + length l2 down to (S (length xs)) + length l2 which crunches down again to S (length xs + length l2). This leaves us with the goal:
  S (length (append xs l2)) = S (length xs + length l2)
Now if only we happened to know that length (append xs l2) = length xs + length l2 ... BUT WAIT! That's *exactly* what our induction hypothesis give us! After a little crunching from simpl, the firstorder tactic is smart enought to figure all this out and use the induction hypothesis to finish the proof.
  { simpl. firstorder. }
Qed.

Reverse the elements of a list.
Fixpoint rev A (l: list A) :=
  match l with
    | nil => nil
    | x::xs =>
    
Note: ++ is just an infix operator for append, so:
      rev xs ++ x :: nil
    
is the same as:
      append (rev xs) (x :: nil)
  end.

Eval cbv in (rev (countdown 5)).

Note that the above version of rev is not efficient. We can do better using a helper function to collect the reversed version of the list as we recurse:
Fixpoint rev'_aux A (acc l: list A) :=
  match l with
    | nil => acc
    | x :: xs => rev'_aux (x :: acc) xs
  end.

Definition rev' A (l: list A) :=
  rev'_aux nil l.

While rev' is more efficient than rev, it is a bit trickier to reason about.
To prove rev and rev' are equivalent, we'll need a fact about append:
Lemma append_associative:
  forall A (l1 l2 l3: list A),
  l1 ++ l2 ++ l3 = (l1 ++ l2) ++ l3.
Proof.
It turns out that firstorder already know this one.
  firstorder.
Qed.

Now we need to prove a carefully chosen lemma about rev'_aux.
Lemma rev_rev'_aux:
  forall A (l acc: list A),
  rev'_aux acc l = rev l ++ acc.
Proof.
  intro. intro. induction l.
  { reflexivity. }
  { intro. simpl. rewrite -> IHl. rewrite <- append_associative. reflexivity. }
Qed.

Now we should be able to prove equivalence.
Lemma rev_rev':
  forall A (l: list A), rev l = rev' l.
Proof.
Note: we have to unfold rev first! This allows us to use rev_rev'_aux.
  unfold rev'. intros. rewrite -> rev_rev'_aux. firstorder.
Qed.

Question: Why did we need to prove rev_rev'_aux separately from rev_rev'?
To prove that rev preserved length, we'll need to know a little more about arithmetic.
Lemma length_rev:
  forall A (l: list A), length (rev l) = length l.
We get stuck trying to prove:
  length l + 1 = S (length l)
Abort.

Question: Why do we get stuck?
Print plus.

Coq has a decision procedure for Pressburger arithmetic. It will make short work of our problem.
Require Import Omega.

Lemma length_rev:
  forall A (l: list A), length (rev l) = length l.
Proof.
  intros. induction l.
  { reflexivity. }
  { simpl. rewrite -> length_append. simpl. firstorder. }
Qed.

upto is another useful function for testing.
Definition upto n :=
  rev (countdown n).

We can prove that rev (rev l) = l, but we'll need a lemma about rev and append:
Lemma rev_append:
  forall A (l1 l2: list A), rev (l1 ++ l2) = rev l2 ++ rev l1.
Proof.
  intro. intro. induction l1.
  { simpl. firstorder. }
  { intro. simpl. rewrite -> IHl1. firstorder. }
Qed.

Now for the lemma we really want:
Lemma rev_involutive:
  forall A (l: list A), rev (rev l) = l.
Proof.
  intros. induction l.
  { reflexivity. }
  { simpl. rewrite -> rev_append. simpl. rewrite -> IHl. reflexivity. }
Qed.

The famous map function provides our first useful example of "higher order functions", which is just a fancy way of saying that map is a function which takes another function as an argument (in this case, the argument is named f).
Fixpoint map A B (f: A -> B) (l: list A) :=
  match l with
    
Question: Are these the same nil?
    | nil => nil
    | x::xs => f x :: map f xs
  end.

Eval cbv in (map (fun x => S x) (upto 5)).

Lemma length_map:
  forall A B (f: A -> B) (l: list A), length (map f l) = length l.
Proof.
  intros. induction l.
  { reflexivity. }
  { simpl. rewrite -> IHl. reflexivity. }
Qed.

The also famous fold function.
Fixpoint fold A B (f: A -> B -> B) (l: list A) (b: B) :=
  match l with
    | nil => b
    | x::xs => f x (fold f xs b)
  end.


Definition map' A B (f: A -> B) (l: list A) :=
  fold (fun x fxs => f x :: fxs) l nil.

Lemma map_map':
  forall A B (f: A -> B) (l: list A), map f l = map' f l.
Abort.

Lemma map'_unroll:
  forall A B (f: A -> B) (x: A) (xs: list A),
  map' f (x :: xs) = f x :: map' f xs.
Proof.
  reflexivity.
Qed.

Lemma map_map':
  forall A B (f: A -> B) (l: list A), map f l = map' f l.
Proof.
  intros. induction l.
  { reflexivity. }
  { simpl. rewrite -> map'_unroll. rewrite -> IHl. reflexivity. }
Qed.

This page has been generated by coqdoc