(* Copyright (c) 2011, Thorsten Altenkirch *) (** %\chapter{%##Lists%}%## *) Section Lists. (** Lists are the ubiqitous datastructure in functional programming, as you should know from Haskell. Given a set [A] we define [list A] to be the set of finite sequences of elements of [A]. E.g. the sequence [[1,2,3]] is an element of [list nat]. We can iterate this process and construct lists of lists, e.g. [[[1,2],[3]]] is an element of [list (list nat)]. However lists are uniform, that is all elements need to have the same type so we cannot form a list like [[1,true]] or [[[1,2],3]]. We are going to formally introduce lists using an _inductive definition_ which has a lot in common with the definition of the natural numbers in the previous chapter. And indeed the theory of lists has a lot in common with the theory of the natural number, so we can call this _list arithmetic_. *) (** * Arithmetic for lists *) Set Implicit Arguments. Load Arith. (** We define lists _inductively_. Given a set [A] a list over A is either the empty list [nil] or it is the result of putting an element [a] in fornt of an already constructed list [l], we write [cons a l]. [nil] and [cons] are _constructors_ of [list A], as [0] and [S] (successor) were constructors of [nat]. *) Inductive list (A : Set) : Set := | nil : list A | cons : A -> list A -> list A. Implicit Arguments nil [A]. (** In functional programming [cons] is usually written as an infix operation. In Haskell this is [:] but since this symbol is used for member ship in Coq, we use [::] instead. Hence the meaning of [:] and [::] in Coq and Haskell are exactly swapped.*) Infix "::" := cons (at level 60, right associativity). (** As an example we can define the list [[2,3]] *) Definition l23 : list nat := 2 :: 3 :: nil. (** And by consing another [1] in front we obtain [[1,2,3]]. *) Definition l123 : list nat := 1 :: l23. (** We are going to prove some basic theorems about lists following the development for natural numbers. There we showed that now successor of a natural number is [0] ([peano7]), here we show that no cons list is equal to the enmpty list. *) Theorem nil_cons : forall (A:Set)(x:A) (l:list A), nil <> x :: l. intros. discriminate. Qed. (** The next peano axiom [peano8] expressed the injectivity of the successor. We have a similar statement for lists: if two cons lists are equal then their tail is equal. To prove this we define [tail] as we had define predecessor for numbers. *) Definition tail (A:Set)(l : list A) : list A := match l with | nil => nil | cons a l => l end. (** The proof follows exactly the one for [peano8]. *) Theorem cons_injective : forall (A : Set)(a b : A)(l m : list A), a :: l = b :: m -> l = m. intros A a b l m h. fold (tail (cons a l)). rewrite h. unfold tail. reflexivity. Qed. (** However, unlike [S], [cons] has another argument, the head of the list. We can also show that it is injective in this argument, that is if two cons lists are eqaul thenthere head is equal. There is a slight problem in defining [head], we cannot (as in Haskell) define [head : list A -> A], because it could be that [A] is empty but there is still [nil : list A] and what should the head of this list be? To overcome this issue we define [head : A -> list A -> A] where the first argument is a _dummy argument_ which is returned for the empty list. *) Definition head (A : Set)(x : A)(l : list A) : A := match l with | nil => x | a :: m => a end. (** Once we have defined [head] the proof of injectivity is rather straightforward. *) Theorem cons_injective' : forall (A : Set)(a b : A)(l m : list A), a :: l = b :: m -> a = b. intros A a b l m h. fold (head a (a :: l)). rewrite h. unfold head. reflexivity. Qed. (** As for natural numbers we have also an induction principle for lists: if a property is true for the empty list, and if it holds for a list [l] then it also holds for [cons a l] for any [a], then it holds for all lists. In Coq we use the same tactic [induction] to perform list indiuction. *) Theorem ind_list : forall (A : Set)(P : list A -> Prop), P nil -> (forall (a:A)(l : list A), P l -> P (a :: l)) -> forall l : list A, P l. intros A P hnil hcons l. induction l. exact hnil. apply hcons. exact IHl. Qed. (** * Lists form a monoid *) (** Previously, we defined addition and multiplication for numbers. There is a very useful operation resembling addition for lists: append. We define [app] by _structural recursion_ over lists. The idea is that to append a list to the empty list is just that list, and to append a list to a cons list has the same head as the list and the tail is obtained by recursively appending the list to the tail. *) Fixpoint app (A : Set)(l m:list A) : list A := match l with | nil => m | a :: l' => a :: (app l' m) end. (** As in Haskell we use the inifx operation [++] to denote append. *) Infix "++" := app (right associativity, at level 60). (** As an example we construct the list [[2,3,1,2,3]] by appending [[2,3]] and [[1,2,3]]. *) Eval compute in (l23 ++ l123). (** We show that [list A] with [++] and [nil] forms a monoid. Indeed the proofs are basically the same as for ([nat],[+],0). *) Theorem app_nil_l : forall (A : Set)(l : list A), nil ++ l = l. intros A l. reflexivity. Qed. Theorem app_l_nil : forall (A : Set)(l : list A), l ++ nil = l. intros A l. induction l. reflexivity. simpl. rewrite IHl. reflexivity. Qed. Theorem assoc_app : forall (A : Set)(l m n : list A), l ++ (m ++ n) = (l ++ m) ++ n. intros A l m n. induction l. reflexivity. simpl. rewrite IHl. reflexivity. Qed. (** * Reverse *) (** While there are many similarities between [nat] and [list A] there are important differences. Commutativity [l ++ m = m ++ l] does not hold (what would be a counterexample?). Hence (list A,++,nil) is an example of a non-commutative monoid. Since we commutativity doesn't hold it makes sense to reverse a list (while it didn't make sense to reverse a number). To define reverse, we first define the operation [snoc] which adds an element at the end of a given list. This operation again is defined by primitive recursion. *) Fixpoint snoc (A:Set) (l : list A)(a : A) {struct l} : list A := match l with | nil => a :: nil | b :: m => b :: (snoc m a) end. (** There is an alternative way to define [snoc] just by using [++]. Can you see how? *) (** As an example we put [1] at the end of [[2,3]] *) Eval compute in (snoc l23 1). (** Using snoc it is easy to define [rev] by primitive recursion. The reverse of an empty list is the empty list. To reverse a cons list, reverse its tail and then snoc the head to the end of the result. *) Fixpoint rev (A:Set)(l : list A) : list A := match l with | nil => nil | a :: l' => snoc (rev l') a end. (** This definition of [rev] is called _naive reverse_ and it is rather inefficient. Can you see why? How can it be improved? *) (** Some examples. *) Eval compute in rev l123. Eval compute in rev (rev l123). (** The 2nd example gives rise to a theorem about [rev], namely that to reverse twice is the identity ([rev (rev l) = l]). To prove it we first prove a lemma about [rev] and [snoc]. How did we discover this lemma? *) Lemma revsnoc : forall (A:Set)(l:list A)(a : A), rev (snoc l a) = a :: (rev l). intros A l a. (** We proceed by induction over [l]. *) induction l. simpl. reflexivity. simpl. rewrite IHl. simpl. reflexivity. Qed. (** And now we can prove the theorem. *) Theorem revrev : forall (A:Set)(l:list A),rev (rev l) = l. intros A l. induction l. simpl. reflexivity. simpl. (** And now it seems that [revsnoc] is exactly what we need. Lucky that we proved it already. *) rewrite revsnoc. rewrite IHl. reflexivity. Qed. (** * Insertion sort *) (** Our next example is sorting: we want to sort a given lists according to an given order. E.g. the list [4 :: 2 :: 3 :: 1 :: nil] should be sorted into [1 :: 2 :: 3 :: 4 :: nil] We will implement and verify "insertion sort". To keep things simple we will sort lists of natural numbers wrt to the <= order. First we implement a boolean function which compares two numbers: *) Fixpoint leqb (m n : nat) {struct m} : bool := match m with | 0 => true | S m => match n with | 0 => false | S n => leqb m n end end. Eval compute in leqb 3 4. Eval compute in leqb 4 3. Notation "m <= n" := (leq m n). (** We just assume that [leq] decided [<=]. I leave it as an exercise to formally prove this, i.e. to replace the axioms by lemmas or theorems. *) Axiom leq1 : forall m n : nat, leqb m n = true -> m <= n. Axiom leq2 : forall m n : nat, m <= n -> leqb m n = true. (** The main function of insertion sort is the function insert which inserts a new element into an already sorted list: *) Fixpoint insert (n:nat)(ms : list nat) {struct ms} : list nat := match ms with | nil => n::nil | m::ms' => if leqb n m then n::ms else m::(insert n ms') end. Eval compute in insert 3 (1::2::4::nil). (** Now sort builds a sorted list from any list by inserting each element into the empty list. *) Fixpoint sort (ms : list nat) : list nat := match ms with | nil => nil | m::ms' => insert m (sort ms') end. Eval compute in sort (4::2::3::1::nil). (* However, how do we know that sort will work for all lists? We are going to verify sort. First we have to define what we mean by sorted. *) Fixpoint Sorted (l : list nat) : Prop := match l with | nil => True | a :: m => Sorted m /\ a <= head a m end. (** Here is another assumption about [<=] I am not going to prove but leave as an exercise. *) Axiom total : forall m n : nat, m <= n \/ n <= m. (** Our goal is to show that [insert] preserves sortedness, i.e. [Sorted l -> Sorted (insert n l)]. To prove this we need to lemmas. *) (** The first one is useful in the case when the new element is not smaller than the current head. In this case we need to know that the head is smaller than the new element so that we can insert it later. *) Lemma leqFalse : forall m n : nat, leqb m n = false -> n <= m. intros m n h. destruct (total m n) as [mn | nm]. assert (mnt : leqb m n = true). apply leq2. exact mn. rewrite h in mnt. discriminate mnt. exact nm. Qed. (** The other lemma is a little case analysis: the head of the result of [insert] is either the inserted element or the previous head. *) Lemma insertSortCase : forall (n a : nat)(l : list nat), head a (insert n l) = n \/ head a (insert n l) = head a l. intros n a l. (** While we say [induction] we are not going to use the induction hypothesis here. So we could have used [destruct] on lists here. *) induction l. left. simpl. reflexivity. simpl. destruct (leqb n a0). left. simpl. reflexivity. right. simpl. reflexivity. Qed. (** We are now able to prove the main lemma on [insert]. *) Lemma insertSorted : forall (n : nat)(l : list nat), Sorted l -> Sorted (insert n l). intros n l. (** We prove the implication by induction. Why did we not do another [intro]? *) induction l. (** The case for the empty list is easy. *) intro h. simpl. split. split. apply le_refl. (** Now the cons case *) intro h. simpl. simpl in h. destruct h as [sl al]. (** We now analyze the result of the comparison. *) case_eq (leqb n a). (** First case [leqb n a = true], that is the element is put in front. *) intro na. simpl. split. split. exact sl. exact al. (** Here we need the correctness of [leq] wrt [<=]. *) apply leq1. exact na. (** Second case [leqb n a = false] so we insert [a] in the tail Here we need our lemmas. *) intro na. simpl. split. apply IHl. exact sl. (** Here we have to reason about the head of [insert n l], so we use our lemma. *) destruct (insertSortCase n a l) as [H1 | H2]. (** First case: it is the new element. *) rewrite H1. apply leqFalse. exact na. (** Second case: it is the old head. *) rewrite H2. exact al. Qed. (** using the previous lemma it is easy to prove our main theorem. *) Theorem sortSorted : forall ms:list nat,Sorted (sort ms). induction ms. (** case ms=nil: *) simpl. split. (** case a::ms *) simpl. apply insertSorted. exact IHms. Qed. (** Is this enough? No, we could have implemented a function with the property sort_ok by always returning the empty list. Another important property of a sorting function is that it returns a permutation of the input. I leave this as an exercise. *) End Lists.