Week 2: Polymorphism
- Author
Adam Chlipala, with modifications by CS-428 staff.
- License
No redistribution allowed (usage by permission in CS-428).
One of the recurring ingredients in effective machine formalization is data structures, and, as in general programming, we want to be able to define data structures that are generic in the kinds of data they contain. Coq supports such things in roughly the same way as Haskell and OCaml, via parametric polymorphism, which is closely related to the idea of generics in languages like Java.
Options
Our first example: the option
type family. While Java and friends force
all sorts of different types to include the special value null
, in Coq we
request that option explicitly by wrapping a type in option
. Specifically,
any value of type option A
, for some type A
, is either None
(sort of
like null
) or Some v
for a v
of type A
.
Inductive option {A : Set} : Set := | None | Some (v : A). Arguments option : clear implicits.
Here are a few example terms using option
.
Example no_number : option nat := None. Example a_number : option nat := Some 42. Example no_number_squared : option (option nat) := None. Example no_number_squared_inside : option (option nat) := Some None. Example a_number_squared : option (option nat) := Some (Some 42).
Pattern matching is the key ingredient for working with inductive definitions
of all sorts. Here are some examples matching on option
s.
Definition increment_optional (no : option nat) : option nat :=
match no with
| None => None
| Some n => Some (n + 1)
end.
Here we use type A * B
of pairs, inhabited by values (a, b)
, with
a : A
and b : B
.
Definition add_optional (po : option (nat * nat)) : option nat :=
match po with
| None => None
| Some (n, m) => Some (n + m)
end.
Lists
For functional programming (as in Coq), the king of all generic data structures is the list.
Inductive list {A : Set} : Set := | nil | cons (hd : A) (tl : @list A). Arguments list: clear implicits.
nil
is the empty list, while cons
, standing for "construct," extends a
list of length n
into one of length n+1
.
Here are some simple lists.
Example nats0 : list nat := nil. Example nats1 : list nat := cons 1 nil. Example nats2 : list nat := cons 1 (cons 2 nil).
Coq features a wonderful notation system, to help us write more concise and readable code after introducing new syntactic forms. We will not give a systematic presentation of the notation system, but we will show many examples, from which it is possible to infer generality by scientific induction. And, of course, the interested reader can always check the notations chapter of the Coq reference manual.
First, our examples can get more readable with an infix operator for cons
.
Infix "::" := cons. Example nats1' : list nat := 1 :: nil. Example nats2' : list nat := 1 :: 2 :: nil.
Getting even more fancy, we declare a notation for list literals.
Notation "[ ]" := nil.Example nats0'' : list nat := []. Example nats1'' : list nat := [1]. Example nats2'' : list nat := [1; 2]. Example nats3'' : list nat := [1; 2; 3].
Here are some classic recursive functions that operate over lists. First, here is how to compute the length of a list. We put implicit function arguments in curly braces, asking Coq to infer them at call sites.
Fixpoint length {A} (ls : list A) : nat :=
match ls with
| nil => 0
| _ :: ls' => 1 + length ls'
end.
Here are two classic recursive functions on lists.
First, concatenation:
Fixpoint app {A} (ls1 ls2 : list A) : list A := match ls1 with | nil => ls2 | x :: ls1' => x :: app ls1' ls2 end. Infix "++" := app.
Next, reversal:
Fixpoint rev {A} (ls : list A) : list A := match ls with | nil => nil | x :: ls' => rev ls' ++ [x] end.
forall (A : Set) (ls1 ls2 : list A), length (ls1 ++ ls2) = length ls1 + length ls2induction ls1; simpl in *; congruence. Qed.forall (A : Set) (ls1 ls2 : list A), length (ls1 ++ ls2) = length ls1 + length ls2
One of the classic gotchas in functional-programming class is how slow this
naive rev
is. Each app
operation requires linear time, so running
linearly many app`s brings us to quadratic time for `rev
. Using a helper
function, we can bring rev
to its optimal linear time.
Fixpoint rev_append {A} (ls acc : list A) : list A :=
match ls with
| nil => acc
| x :: ls' => rev_append ls' (x :: acc)
end.
This function rev_append
takes an extra accumulator argument, in which we
gradually build up the original input in reversed order. The base case just
returns the accumulator. Now reversal just needs to do a rev_append
with
an empty initial accumulator.
Definition rev' {A} (ls : list A) : list A :=
rev_append ls [].
A few test destruct can help convince us that this seems to work.
OK, great. Now it seems worth investing in a correctness proof. We'll discover it interactively in class, but here's a worked-out final answer, with the several lemmas that we discover are useful.
List concatenation is associative.
forall (A : Set) (ls1 ls2 ls3 : list A), (ls1 ++ ls2) ++ ls3 = ls1 ++ ls2 ++ ls3forall (A : Set) (ls1 ls2 ls3 : list A), (ls1 ++ ls2) ++ ls3 = ls1 ++ ls2 ++ ls3A: Set
ls2, ls3: list Als2 ++ ls3 = ls2 ++ ls3A: Set
hd: A
ls1: list A
IHls1: forall ls2 ls3 : list A, (ls1 ++ ls2) ++ ls3 = ls1 ++ ls2 ++ ls3
ls2, ls3: list Ahd :: (ls1 ++ ls2) ++ ls3 = hd :: ls1 ++ ls2 ++ ls3congruence.A: Set
ls2, ls3: list Als2 ++ ls3 = ls2 ++ ls3A: Set
hd: A
ls1: list A
IHls1: forall ls2 ls3 : list A, (ls1 ++ ls2) ++ ls3 = ls1 ++ ls2 ++ ls3
ls2, ls3: list Ahd :: (ls1 ++ ls2) ++ ls3 = hd :: ls1 ++ ls2 ++ ls3congruence. Qed.A: Set
hd: A
ls1: list A
IHls1: forall ls2 ls3 : list A, (ls1 ++ ls2) ++ ls3 = ls1 ++ ls2 ++ ls3
ls2, ls3: list Ahd :: ls1 ++ ls2 ++ ls3 = hd :: ls1 ++ ls2 ++ ls3
The natural correctness condition for rev_append
: it does what it says on
the package, combining reversal with appending!
forall (A : Set) (ls acc : list A), rev_append ls acc = rev ls ++ accforall (A : Set) (ls acc : list A), rev_append ls acc = rev ls ++ accA: Set
acc: list Aacc = accA: Set
hd: A
ls: list A
IHls: forall acc : list A, rev_append ls acc = rev ls ++ acc
acc: list Arev_append ls (hd :: acc) = (rev ls ++ [hd]) ++ acccongruence.A: Set
acc: list Aacc = accA: Set
hd: A
ls: list A
IHls: forall acc : list A, rev_append ls acc = rev ls ++ acc
acc: list Arev_append ls (hd :: acc) = (rev ls ++ [hd]) ++ accA: Set
hd: A
ls: list A
IHls: forall acc : list A, rev_append ls acc = rev ls ++ acc
acc: list Arev ls ++ hd :: acc = (rev ls ++ [hd]) ++ accA: Set
hd: A
ls: list A
IHls: forall acc : list A, rev_append ls acc = rev ls ++ acc
acc: list Arev ls ++ hd :: acc = rev ls ++ [hd] ++ acccongruence. Qed.A: Set
hd: A
ls: list A
IHls: forall acc : list A, rev_append ls acc = rev ls ++ acc
acc: list Arev ls ++ hd :: acc = rev ls ++ hd :: acc
Concatenating the empty list has no effect.
forall (A : Set) (ls : list A), ls ++ [ ] = lsforall (A : Set) (ls : list A), ls ++ [ ] = lsall: congruence. Qed.A: Set[ ] = [ ]A: Set
hd: A
ls: list A
IHls: ls ++ [ ] = lshd :: ls ++ [ ] = hd :: ls
Now we can prove equivalence of rev'
and rev
, with no new induction.
forall (A : Set) (ls : list A), rev' ls = rev lsforall (A : Set) (ls : list A), rev' ls = rev lsA: Set
ls: list Arev' ls = rev lsA: Set
ls: list Arev_append ls [ ] = rev lsapply app_nil. Qed.A: Set
ls: list Arev ls ++ [ ] = rev ls
Zipping and unzipping
Another classic pair of list operations is zipping and unzipping. These functions convert between pairs of lists and lists of pairs.
Fixpoint zip {A1 A2} (ls1 : list A1) (ls2 : list A2) : list (A1 * A2) :=
match ls1, ls2 with
| x1 :: ls1', x2 :: ls2' => (x1, x2) :: zip ls1' ls2'
| _, _ => []
end.
Note how, when passed two lengths of different lists, zip
drops the
mismatched suffix of the longer list.
An explicit Set
annotation is needed here, for obscure type-inference
reasons.
Fixpoint unzip {A1 A2 : Set} (ls : list (A1 * A2))
: list A1 * list A2 :=
match ls with
| [] => ([], [])
| (x1, x2) :: ls' =>
let (ls1, ls2) := unzip ls' in
(x1 :: ls1, x2 :: ls2)
end.
A few common-sense properties hold of these definitions.
forall (A1 A2 : Set) (ls1 : list A1) (ls2 : list A2), length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)forall (A1 A2 : Set) (ls1 : list A1) (ls2 : list A2), length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)A1, A2: Set
ls2: list A20 = 0A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)
ls2: list A2length match ls2 with | [ ] => [ ] | x2 :: ls2' => (hd, x2) :: zip ls1 ls2' end = match length ls2 with | 0 => 0 | S m' => S (Nat.min (length ls1) m') endlia.A1, A2: Set
ls2: list A20 = 0A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)
ls2: list A2length match ls2 with | [ ] => [ ] | x2 :: ls2' => (hd, x2) :: zip ls1 ls2' end = match length ls2 with | 0 => 0 | S m' => S (Nat.min (length ls1) m') endA1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)0 = 0A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)
hd0: A2
ls2: list A2S (length (zip ls1 ls2)) = S (Nat.min (length ls1) (length ls2))lia.A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)0 = 0A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)
hd0: A2
ls2: list A2S (length (zip ls1 ls2)) = S (Nat.min (length ls1) (length ls2))lia. Qed.A1, A2: Set
hd: A1
ls1: list A1
IHls1: forall ls2 : list A2, length (zip ls1 ls2) = Nat.min (length ls1) (length ls2)
hd0: A2
ls2: list A2S (Nat.min (length ls1) (length ls2)) = S (Nat.min (length ls1) (length ls2))
We write fst
and snd
for the first and second projection operators on
pairs, respectively.
forall (A1 A2 : Set) (ls : list (A1 * A2)), length (fst (unzip ls)) = length lsforall (A1 A2 : Set) (ls : list (A1 * A2)), length (fst (unzip ls)) = length lsA1, A2: Set0 = 0A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: length (fst (unzip ls)) = length lslength (fst (let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2))) = S (length ls)congruence.A1, A2: Set0 = 0A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: length (fst (unzip ls)) = length lslength (fst (let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2))) = S (length ls)A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
IHls: length (fst (unzip ls)) = length lslength (fst (let (ls1, ls2) := unzip ls in (a :: ls1, a0 :: ls2))) = S (length ls)
Note how destruct
allows us to pull apart a pair into its two pieces.
A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: length (fst (l, l0)) = length lslength (fst (a :: l, a0 :: l0)) = S (length ls)congruence. Qed.A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: length l = length lsS (length l) = S (length ls)forall (A1 A2 : Set) (ls : list (A1 * A2)), length (snd (unzip ls)) = length lsforall (A1 A2 : Set) (ls : list (A1 * A2)), length (snd (unzip ls)) = length lsA1, A2: Set0 = 0A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: length (snd (unzip ls)) = length lslength (snd (let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2))) = S (length ls)congruence.A1, A2: Set0 = 0A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: length (snd (unzip ls)) = length lslength (snd (let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2))) = S (length ls)A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
IHls: length (snd (unzip ls)) = length lslength (snd (let (ls1, ls2) := unzip ls in (a :: ls1, a0 :: ls2))) = S (length ls)A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: length (snd (l, l0)) = length lslength (snd (a :: l, a0 :: l0)) = S (length ls)congruence. Qed.A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: length l0 = length lsS (length l0) = S (length ls)forall (A1 A2 : Set) (ls : list (A1 * A2)), (let (ls1, ls2) := unzip ls in zip ls1 ls2) = lsforall (A1 A2 : Set) (ls : list (A1 * A2)), (let (ls1, ls2) := unzip ls in zip ls1 ls2) = lsA1, A2: Set[ ] = [ ]A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: (let (ls1, ls2) := unzip ls in zip ls1 ls2) = ls(let (ls1, ls2) := let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2) in zip ls1 ls2) = hd :: lscongruence.A1, A2: Set[ ] = [ ]A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: (let (ls1, ls2) := unzip ls in zip ls1 ls2) = ls(let (ls1, ls2) := let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2) in zip ls1 ls2) = hd :: lsA1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
IHls: (let (ls1, ls2) := unzip ls in zip ls1 ls2) = ls(let (ls1, ls2) := let (ls1, ls2) := unzip ls in (a :: ls1, a0 :: ls2) in zip ls1 ls2) = (a, a0) :: lsA1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: zip l l0 = lszip (a :: l) (a0 :: l0) = (a, a0) :: lscongruence. Qed.A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: zip l l0 = ls(a, a0) :: zip l l0 = (a, a0) :: ls
There are also interesting interactions with app
and rev
.
forall (A1 A2 : Set) (x y : list (A1 * A2)), unzip (x ++ y) = (let (x1, x2) := unzip x in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))forall (A1 A2 : Set) (x y : list (A1 * A2)), unzip (x ++ y) = (let (x1, x2) := unzip x in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))A1, A2: Set
y: list (A1 * A2)unzip y = (let (y1, y2) := unzip y in (y1, y2))A1, A2: Set
hd: (A1 * A2)%type
x: list (A1 * A2)
IHx: forall y : list (A1 * A2), unzip (x ++ y) = (let (x1, x2) := unzip x in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))
y: list (A1 * A2)(let (x1, x2) := hd in let (ls1, ls2) := unzip (x ++ y) in (x1 :: ls1, x2 :: ls2)) = (let (x1, x2) := let (x1, x2) := hd in let (ls1, ls2) := unzip x in (x1 :: ls1, x2 :: ls2) in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))A1, A2: Set
y: list (A1 * A2)unzip y = (let (y1, y2) := unzip y in (y1, y2))congruence.A1, A2: Set
y: list (A1 * A2)
l: list A1
l0: list A2(l, l0) = (l, l0)A1, A2: Set
hd: (A1 * A2)%type
x: list (A1 * A2)
IHx: forall y : list (A1 * A2), unzip (x ++ y) = (let (x1, x2) := unzip x in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))
y: list (A1 * A2)(let (x1, x2) := hd in let (ls1, ls2) := unzip (x ++ y) in (x1 :: ls1, x2 :: ls2)) = (let (x1, x2) := let (x1, x2) := hd in let (ls1, ls2) := unzip x in (x1 :: ls1, x2 :: ls2) in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))A1, A2: Set
hd: (A1 * A2)%type
x: list (A1 * A2)
IHx: forall y : list (A1 * A2), unzip (x ++ y) = (let (x1, x2) := unzip x in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))
y: list (A1 * A2)(let (x1, x2) := hd in let (ls1, ls2) := let (x3, x4) := unzip x in let (y1, y2) := unzip y in (x3 ++ y1, x4 ++ y2) in (x1 :: ls1, x2 :: ls2)) = (let (x1, x2) := let (x1, x2) := hd in let (ls1, ls2) := unzip x in (x1 :: ls1, x2 :: ls2) in let (y1, y2) := unzip y in (x1 ++ y1, x2 ++ y2))simpl in *; congruence. Qed.A1, A2: Set
a: A1
a0: A2
x: list (A1 * A2)
l: list A1
l0: list A2
IHx: forall y : list (A1 * A2), unzip (x ++ y) = (let (y1, y2) := unzip y in (l ++ y1, l0 ++ y2))
y: list (A1 * A2)
l1: list A1
l2: list A2(a :: l ++ l1, a0 :: l0 ++ l2) = ((a :: l) ++ l1, (a0 :: l0) ++ l2)forall (A1 A2 : Set) (ls : list (A1 * A2)), unzip (rev ls) = (let (ls1, ls2) := unzip ls in (rev ls1, rev ls2))forall (A1 A2 : Set) (ls : list (A1 * A2)), unzip (rev ls) = (let (ls1, ls2) := unzip ls in (rev ls1, rev ls2))A1, A2: Set([ ], [ ]) = ([ ], [ ])A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: unzip (rev ls) = (let (ls1, ls2) := unzip ls in (rev ls1, rev ls2))unzip (rev ls ++ [hd]) = (let (ls1, ls2) := let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2) in (rev ls1, rev ls2))congruence.A1, A2: Set([ ], [ ]) = ([ ], [ ])A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: unzip (rev ls) = (let (ls1, ls2) := unzip ls in (rev ls1, rev ls2))unzip (rev ls ++ [hd]) = (let (ls1, ls2) := let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2) in (rev ls1, rev ls2))A1, A2: Set
hd: (A1 * A2)%type
ls: list (A1 * A2)
IHls: unzip (rev ls) = (let (ls1, ls2) := unzip ls in (rev ls1, rev ls2))(let (x1, x2) := let (ls1, ls2) := unzip ls in (rev ls1, rev ls2) in let (y1, y2) := let (x3, x4) := hd in ([x3], [x4]) in (x1 ++ y1, x2 ++ y2)) = (let (ls1, ls2) := let (x1, x2) := hd in let (ls1, ls2) := unzip ls in (x1 :: ls1, x2 :: ls2) in (rev ls1, rev ls2))congruence. Qed.A1, A2: Set
a: A1
a0: A2
ls: list (A1 * A2)
l: list A1
l0: list A2
IHls: unzip (rev ls) = (rev l, rev l0)(rev l ++ [a], rev l0 ++ [a0]) = (rev l ++ [a], rev l0 ++ [a0])
Binary trees
Another classic datatype is binary trees, which we can define like so.
Inductive tree {A : Set} : Set := | Leaf | Node (l : @tree A) (d : A) (r : @tree A). Arguments tree: clear implicits. Example tr1 : tree nat := Node (Node Leaf 7 Leaf) 8 (Node Leaf 9 (Node Leaf 10 Leaf)).
There is a natural notion of size of a tree.
Fixpoint size {A} (t : tree A) : nat :=
match t with
| Leaf => 0
| Node l _ r => 1 + size l + size r
end.
There is also a natural sense of reversing a tree, flipping it around its vertical axis.
Fixpoint reverse {A} (t : tree A) : tree A :=
match t with
| Leaf => Leaf
| Node l d r => Node (reverse r) d (reverse l)
end.
There is a natural relationship between the two.
forall (A : Set) (t : tree A), size (reverse t) = size tinduction t; simpl; (congruence || lia). Qed.forall (A : Set) (t : tree A), size (reverse t) = size t
Another classic tree operation is flattening into lists.
Fixpoint flatten {A} (t : tree A) : list A :=
match t with
| Leaf => []
| Node l d r => flatten l ++ d :: flatten r
end.
Note here that operators ++
and ::
are right-associative.
forall (A : Set) (t : tree A), length (flatten t) = size tforall (A : Set) (t : tree A), length (flatten t) = size tA: Set0 = 0A: Set
t1: tree A
d: A
t2: tree A
IHt1: length (flatten t1) = size t1
IHt2: length (flatten t2) = size t2length (flatten t1 ++ d :: flatten t2) = S (size t1 + size t2)congruence.A: Set0 = 0rewrite length_app; simpl in *; lia. Qed.A: Set
t1: tree A
d: A
t2: tree A
IHt1: length (flatten t1) = size t1
IHt2: length (flatten t2) = size t2length (flatten t1 ++ d :: flatten t2) = S (size t1 + size t2)forall (A : Set) (ls1 ls2 : list A), rev (ls1 ++ ls2) = rev ls2 ++ rev ls1forall (A : Set) (ls1 ls2 : list A), rev (ls1 ++ ls2) = rev ls2 ++ rev ls1A: Set
ls2: list Arev ls2 = rev ls2 ++ [ ]A: Set
hd: A
ls1: list A
IHls1: forall ls2 : list A, rev (ls1 ++ ls2) = rev ls2 ++ rev ls1
ls2: list Arev (ls1 ++ ls2) ++ [hd] = rev ls2 ++ rev ls1 ++ [hd]A: Set
ls2: list Arev ls2 = rev ls2 ++ [ ]congruence.A: Set
ls2: list Arev ls2 = rev ls2A: Set
hd: A
ls1: list A
IHls1: forall ls2 : list A, rev (ls1 ++ ls2) = rev ls2 ++ rev ls1
ls2: list Arev (ls1 ++ ls2) ++ [hd] = rev ls2 ++ rev ls1 ++ [hd]apply app_assoc. Qed.A: Set
hd: A
ls1: list A
IHls1: forall ls2 : list A, rev (ls1 ++ ls2) = rev ls2 ++ rev ls1
ls2: list A(rev ls2 ++ rev ls1) ++ [hd] = rev ls2 ++ rev ls1 ++ [hd]forall (A : Set) (t : tree A), rev (flatten t) = flatten (reverse t)forall (A : Set) (t : tree A), rev (flatten t) = flatten (reverse t)A: Set[ ] = [ ]A: Set
t1: tree A
d: A
t2: tree A
IHt1: rev (flatten t1) = flatten (reverse t1)
IHt2: rev (flatten t2) = flatten (reverse t2)rev (flatten t1 ++ d :: flatten t2) = flatten (reverse t2) ++ d :: flatten (reverse t1)congruence.A: Set[ ] = [ ]A: Set
t1: tree A
d: A
t2: tree A
IHt1: rev (flatten t1) = flatten (reverse t1)
IHt2: rev (flatten t2) = flatten (reverse t2)rev (flatten t1 ++ d :: flatten t2) = flatten (reverse t2) ++ d :: flatten (reverse t1)A: Set
t1: tree A
d: A
t2: tree A
IHt1: rev (flatten t1) = flatten (reverse t1)
IHt2: rev (flatten t2) = flatten (reverse t2)(rev (flatten t2) ++ [d]) ++ rev (flatten t1) = flatten (reverse t2) ++ d :: flatten (reverse t1)congruence. Qed.A: Set
t1: tree A
d: A
t2: tree A
IHt1: rev (flatten t1) = flatten (reverse t1)
IHt2: rev (flatten t2) = flatten (reverse t2)rev (flatten t2) ++ d :: rev (flatten t1) = flatten (reverse t2) ++ d :: flatten (reverse t1)
Syntax trees
Trees are particularly important to us in studying program proof, since it is natural to represent programs as syntax trees. Here's a quick example, for a tiny imperative language.
Notation var := string. Inductive expression : Set := | Const (n : nat) | Var (x : var) | Plus (e1 e2 : expression) | Minus (e1 e2 : expression) | Times (e1 e2 : expression) | GreaterThan (e1 e2 : expression) | Not (e : expression). Inductive statement : Set := | Assign (x : var) (e : expression) | Sequence (s1 s2 : statement) | IfThenElse (e : expression) (s1 s2 : statement) | WhileLoop (e : expression) (s : statement).
First, here's a quick sample of nifty notations to write almost-natural-looking embedded programs in Coq.
Declare Custom Entry prog. Coercion Const : nat >-> expression. Coercion Var : string >-> expression. Infix "+" := Plus (in custom prog at level 50). Infix "-" := Minus (in custom prog at level 50). Infix "*" := Times (in custom prog at level 40). Infix ">" := GreaterThan (in custom prog at level 70). Infix ":=" := Assign (in custom prog at level 75). Infix ";" := Sequence (in custom prog at level 76). Notation "'if' ( e ) { s1 } 'else' { s2 }" := (IfThenElse e s1 s2) (in custom prog at level 75).Notation "c" := c (in custom prog at level 1, c constr at level 0). Notation "[[ e ]]" := e (e custom prog). Example factorial := [[ "answer" := 1; while ("input" > 0) { "answer" := "answer" * "input"; "input" := "input" - 1 } ]].
A variety of compiler-style operations can be coded on top of this type. Here's one to count total variable occurrences.
Fixpoint varsInExpression (e : expression) : nat := match e with | Const _ => 0 | Var _ => 1 | Plus e1 e2 | Minus e1 e2 | Times e1 e2 | GreaterThan e1 e2 => varsInExpression e1 + varsInExpression e2 | Not e1 => varsInExpression e1 end. Fixpoint varsInStatement (s : statement) : nat := match s with | Assign _ e => 1 + varsInExpression e | Sequence s1 s2 => varsInStatement s1 + varsInStatement s2 | IfThenElse e s1 s2 => varsInExpression e + varsInStatement s1 + varsInStatement s2 | WhileLoop e s1 => varsInExpression e + varsInStatement s1 end.
We will need to wait for a few more lectures' worth of conceptual progress before we can prove that transformations on programs preserve meaning, but we do already have enough tools that prove that transformations preserve more basic properties, like number of variables. Here's one such transformation, which flips "then" and "else" cases while also negating "if" conditions.
Fixpoint flipper (s : statement) : statement := match s with | Assign _ _ => s | Sequence s1 s2 => Sequence (flipper s1) (flipper s2) | IfThenElse e s1 s2 => IfThenElse (Not e) (flipper s2) (flipper s1) | WhileLoop e s1 => WhileLoop e (flipper s1) end.forall s : statement, varsInStatement (flipper s) = varsInStatement sforall s : statement, varsInStatement (flipper s) = varsInStatement sall: congruence || lia. Qed.x: var
e: expressionS (varsInExpression e) = S (varsInExpression e)s1, s2: statement
IHs1: varsInStatement (flipper s1) = varsInStatement s1
IHs2: varsInStatement (flipper s2) = varsInStatement s2varsInStatement (flipper s1) + varsInStatement (flipper s2) = varsInStatement s1 + varsInStatement s2e: expression
s1, s2: statement
IHs1: varsInStatement (flipper s1) = varsInStatement s1
IHs2: varsInStatement (flipper s2) = varsInStatement s2varsInExpression e + varsInStatement (flipper s2) + varsInStatement (flipper s1) = varsInExpression e + varsInStatement s1 + varsInStatement s2e: expression
s: statement
IHs: varsInStatement (flipper s) = varsInStatement svarsInExpression e + varsInStatement (flipper s) = varsInExpression e + varsInStatement s
Just for the sheer madcap fun of it, let's write some translations of programs into our lists from before, with variables as data values.
Fixpoint listifyExpression (e : expression) : list var := match e with | Const _ => [] | Var x => [x] | Plus e1 e2 | Minus e1 e2 | Times e1 e2 | GreaterThan e1 e2 => listifyExpression e1 ++ listifyExpression e2 | Not e1 => listifyExpression e1 end. Fixpoint listifyStatement (s : statement) : list var := match s with | Assign x e => x :: listifyExpression e | Sequence s1 s2 => listifyStatement s1 ++ listifyStatement s2 | IfThenElse e s1 s2 => listifyExpression e ++ listifyStatement s1 ++ listifyStatement s2 | WhileLoop e s1 => listifyExpression e ++ listifyStatement s1 end.
At this point, I can't resist switching to a more automated proof style, though still a pretty tame one.
#[local] Hint Rewrite length_app : poly.forall e : expression, length (listifyExpression e) = varsInExpression einduction e; simpl; autorewrite with poly; lia. Qed. #[local] Hint Rewrite length_listifyExpression : poly.forall e : expression, length (listifyExpression e) = varsInExpression eforall s : statement, length (listifyStatement s) = varsInStatement sinduction s; simpl; autorewrite with poly; lia. Qed.forall s : statement, length (listifyStatement s) = varsInStatement s
Other transformations are also possible, like the Swedish-Chef optimization, which turns every variable into "bork". It saves many bits when most variable names are longer than 4 characters.
Fixpoint swedishExpression (e : expression) : expression := match e with | Const _ => e | Var _ => Var "bork" | Plus e1 e2 => Plus (swedishExpression e1) (swedishExpression e2) | Minus e1 e2 => Minus (swedishExpression e1) (swedishExpression e2) | Times e1 e2 => Times (swedishExpression e1) (swedishExpression e2) | GreaterThan e1 e2 => GreaterThan (swedishExpression e1) (swedishExpression e2) | Not e1 => Not (swedishExpression e1) end. Fixpoint swedishStatement (s : statement) : statement := match s with | Assign _ e => Assign "bork" (swedishExpression e) | Sequence s1 s2 => Sequence (swedishStatement s1) (swedishStatement s2) | IfThenElse e s1 s2 => IfThenElse (swedishExpression e) (swedishStatement s1) (swedishStatement s2) | WhileLoop e s1 => WhileLoop (swedishExpression e) (swedishStatement s1) end.Fixpoint swedishList (ls : list var) : list var := match ls with | [] => [] | _ :: ls => "bork" :: swedishList ls end.forall ls1 ls2 : list var, swedishList (ls1 ++ ls2) = swedishList ls1 ++ swedishList ls2induction ls1; simpl in *; congruence. Qed. #[local] Hint Rewrite swedishList_app : poly.forall ls1 ls2 : list var, swedishList (ls1 ++ ls2) = swedishList ls1 ++ swedishList ls2forall e : expression, listifyExpression (swedishExpression e) = swedishList (listifyExpression e)induction e; simpl in *; autorewrite with poly; congruence. Qed. #[local] Hint Rewrite listifyExpression_swedishExpression : poly.forall e : expression, listifyExpression (swedishExpression e) = swedishList (listifyExpression e)forall s : statement, listifyStatement (swedishStatement s) = swedishList (listifyStatement s)induction s; simpl in *; autorewrite with poly; congruence. Qed.forall s : statement, listifyStatement (swedishStatement s) = swedishList (listifyStatement s)