- Files:
Lists
The type of list is both recursive and has a type parameter.
Inductive list (A : Type) :=
| nil : list A
| cons : A -> list A -> list A.We declare arguments as implicit. Note here we can specify a prefix if we want.
Arguments nil {A}. Arguments cons {A}. Set Printing Implicit.Unset Printing Implicit.
Check now the induction principle we get for lists.
It's saying that to prove P l for some list l, you only need to prove
P nil
P (cons a l')for anyaandl'such thatP l'
Have a look at its type:
Proofs by induction
We reimport all these datatypes from Rocq, to be able to use functions from the standard library.
From Stdlib Require Import Datatypes List. Open Scope nat.
Like for nat, list in the standard library comes with nice notations
to be able to write x :: l or [ a ; b ; c ; d ].
We can load them as follows.
Import ListNotations.Below we check the types of concatenation and reversal of lists.
Lemmas about lists we typically proven by induction as we did for natural numbers.
A: Type
l: list Al ++ [] = lA: Type
l: list Al ++ [] = lA: Type[] ++ [] = []A: Type
a: A
l: list A
IHl: l ++ [] = l(a :: l) ++ [] = a :: lA: Type[] = []A: Type
a: A
l: list A
IHl: l ++ [] = la :: l ++ [] = a :: lreflexivity.A: Type[] = []A: Type
a: A
l: list A
IHl: l ++ [] = la :: l ++ [] = a :: lassumption. Qed.A: Type
a: A
l: list A
IHl: l ++ [] = ll ++ [] = lA: Type
l1, l2: list Arev (l1 ++ l2) = rev l2 ++ rev l1A: Type
l1, l2: list Arev (l1 ++ l2) = rev l2 ++ rev l1A: Type
l2: list Arev ([] ++ l2) = rev l2 ++ rev []A: Type
a: A
l, l2: list A
IHl: rev (l ++ l2) = rev l2 ++ rev lrev ((a :: l) ++ l2) = rev l2 ++ rev (a :: l)A: Type
l2: list Arev l2 = rev l2 ++ []A: Type
a: A
l, l2: list A
IHl: rev (l ++ l2) = rev l2 ++ rev lrev (l ++ l2) ++ [a] = rev l2 ++ rev l ++ [a]A: Type
l2: list Arev l2 = rev l2 ++ []reflexivity.A: Type
l2: list Arev l2 = rev l2A: Type
a: A
l, l2: list A
IHl: rev (l ++ l2) = rev l2 ++ rev lrev (l ++ l2) ++ [a] = rev l2 ++ rev l ++ [a]
For rewrite we can separate two lemmas to rewrite by a comma.
reflexivity. Qed.A: Type
a: A
l, l2: list A
IHl: rev (l ++ l2) = rev l2 ++ rev l(rev l2 ++ rev l) ++ [a] = (rev l2 ++ rev l) ++ [a]A: Type
l: list Arev (rev l) = lA: Type
l: list Arev (rev l) = lA: Typerev (rev []) = []A: Type
a: A
l: list A
IHl: rev (rev l) = lrev (rev (a :: l)) = a :: lA: Type[] = []A: Type
a: A
l: list A
IHl: rev (rev l) = lrev (rev l ++ [a]) = a :: lreflexivity.A: Type[] = []A: Type
a: A
l: list A
IHl: rev (rev l) = lrev (rev l ++ [a]) = a :: lA: Type
a: A
l: list A
IHl: rev (rev l) = lrev [a] ++ rev (rev l) = a :: lA: Type
a: A
l: list A
IHl: rev (rev l) = la :: rev (rev l) = a :: lreflexivity. Qed.A: Type
a: A
l: list A
IHl: rev (rev l) = la :: l = a :: l
The rev defined above is not very smart because it uses concatenation
on the right which is expensive. However, it works as a specification for a
smarter version using an accumulator.
Fixpoint fast_rev {A} (l : list A) (acc : list A) :=
match l with
| [] => acc
| x :: l => fast_rev l (x :: acc)
end.We can show the two behave the same.
A: Type
l: list Afast_rev l [] = rev lA: Type
l: list Afast_rev l [] = rev lA: Typefast_rev [] [] = rev []A: Type
a: A
l: list A
IHl: fast_rev l [] = rev lfast_rev (a :: l) [] = rev (a :: l)A: Type[] = []A: Type
a: A
l: list A
IHl: fast_rev l [] = rev lfast_rev l [a] = rev l ++ [a]reflexivity.A: Type[] = []A: Type
a: A
l: list A
IHl: fast_rev l [] = rev lfast_rev l [a] = rev l ++ [a]
We are stuck again, because the acc argument is changed.
Abort.A: Type
l: list Afast_rev l [] = rev lA: Type
l: list Afast_rev l [] = rev l
We prove a more general lemma locally.
We're using forall here to introduce a parameter.
A: Type
l: list Aforall acc : list A, fast_rev l acc = rev l ++ accA: Type
l: list A
H: forall acc : list A, fast_rev l acc = rev l ++ accfast_rev l [] = rev lA: Type
l: list Aforall acc : list A, fast_rev l acc = rev l ++ accA: Typeforall acc : list A, fast_rev [] acc = rev [] ++ accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ accforall acc : list A, fast_rev (a :: l) acc = rev (a :: l) ++ accA: Typeforall acc : list A, acc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ accforall acc : list A, fast_rev l (a :: acc) = (rev l ++ [a]) ++ accA: Type
acc: list Aacc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Afast_rev l (a :: acc) = (rev l ++ [a]) ++ accreflexivity.A: Type
acc: list Aacc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Afast_rev l (a :: acc) = (rev l ++ [a]) ++ accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Arev l ++ a :: acc = (rev l ++ [a]) ++ accreflexivity.A: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Arev l ++ a :: acc = rev l ++ [a] ++ accA: Type
l: list A
H: forall acc : list A, fast_rev l acc = rev l ++ accfast_rev l [] = rev lA: Type
l: list A
H: forall acc : list A, fast_rev l acc = rev l ++ accrev l ++ [] = rev lA: Type
l: list A
H: forall acc : list A, fast_rev l acc = rev l ++ accrev l ++ [] = rev lreflexivity. Qed.A: Type
l: list A
H: forall acc : list A, fast_rev l acc = rev l ++ accrev l = rev l
We can prove things in a different way.
A: Type
l: list Afast_rev l [] = rev lA: Type
l: list Afast_rev l [] = rev l
We are going to generalise the empty list to any acc.
We write @nil A to provide the type argument A explicitly.
A: Type
l: list Afast_rev l [] = rev l ++ []A: Type
l: list Aforall acc : list A, fast_rev l acc = rev l ++ accA: Typeforall acc : list A, fast_rev [] acc = rev [] ++ accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ accforall acc : list A, fast_rev (a :: l) acc = rev (a :: l) ++ accA: Typeforall acc : list A, acc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ accforall acc : list A, fast_rev l (a :: acc) = (rev l ++ [a]) ++ accA: Type
acc: list Aacc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Afast_rev l (a :: acc) = (rev l ++ [a]) ++ accreflexivity.A: Type
acc: list Aacc = accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Afast_rev l (a :: acc) = (rev l ++ [a]) ++ accA: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Arev l ++ a :: acc = (rev l ++ [a]) ++ accreflexivity. Qed.A: Type
a: A
l: list A
IHl: forall acc : list A, fast_rev l acc = rev l ++ acc
acc: list Arev l ++ a :: acc = rev l ++ [a] ++ acc
Polymorphic iteration
We will now have a look at iter, a function such that iter f n x
applies n times the function f, starting with the argument x.
For instance iter f 2 x is f (f x).
Fixpoint iter {A : Type} (f : A -> A) (n : nat) (x : A) : A :=
match n with
| 0 => x
| S n' => f (iter f n' x)
end.We can show that iterating the successor n times is the same as adding
n.
n, x: natn + x = iter S n xn, x: natn + x = iter S n xx: nat0 + x = iter S 0 xn, x: nat
IH: n + x = iter S n xS n + x = iter S (S n) xx: natx = xn, x: nat
IH: n + x = iter S n xS (n + x) = S (iter S n x)reflexivity.x: natx = xn, x: nat
IH: n + x = iter S n xS (n + x) = S (iter S n x)assumption. Qed.n, x: nat
IH: n + x = iter S n xn + x = iter S n x
Similarly, multiplication is iterated addition.
n, x: natn * x = iter (Nat.add x) n 0n, x: natn * x = iter (Nat.add x) n 0x: nat0 * x = iter (Nat.add x) 0 0n, x: nat
IH: n * x = iter (Nat.add x) n 0S n * x = iter (Nat.add x) (S n) 0x: nat0 = 0n, x: nat
IH: n * x = iter (Nat.add x) n 0x + n * x = x + iter (Nat.add x) n 0reflexivity.x: nat0 = 0n, x: nat
IH: n * x = iter (Nat.add x) n 0x + n * x = x + iter (Nat.add x) n 0assumption. Qed.n, x: nat
IH: n * x = iter (Nat.add x) n 0n * x = iter (Nat.add x) n 0
Mutual inductive types
We will not cover dependent types in a lecture — please have a look at them yourselves.
It is also possible to define several inductive types at the same time.
You just combine them with the with keyword.
This way we can define the type of trees mutually with that of forests (which are basically lists of trees).
Combined Scheme tree_forest_mutind from tree_ind', forest_ind'.
You can then define mutual definitions over such types by using Fixpoint
and the with keyword.
A: Type
t: tree Alength (nodes t) = size tA: Type
t: tree Alength (nodes t) = size tA: Type
t: tree A
H: (forall t : tree A, length (nodes t) = size t) /\ (forall f : forest A, length (nodes_forest f) = size_forest f)length (nodes t) = size tA: Type
t: tree A(forall t0 : tree A, length (nodes t0) = size t0) /\ (forall f : forest A, length (nodes_forest f) = size_forest f)A: Type
t: tree A(forall t0 : tree A, length (nodes t0) = size t0) /\ (forall f : forest A, length (nodes_forest f) = size_forest f)A: Typeforall (a : A) (f : forest A), length (nodes_forest f) = size_forest f -> length (nodes (nnode A a f)) = size (nnode A a f)A: Typelength (nodes_forest (nnil A)) = size_forest (nnil A)A: Typeforall t : tree A, length (nodes t) = size t -> forall f : forest A, length (nodes_forest f) = size_forest f -> length (nodes_forest (ncons A t f)) = size_forest (ncons A t f)A: Typeforall (a : A) (f : forest A), length (nodes_forest f) = size_forest f -> length (nodes (nnode A a f)) = size (nnode A a f)A: Type
a: A
f: forest A
IH: length (nodes_forest f) = size_forest flength (nodes (nnode A a f)) = size (nnode A a f)now rewrite IH.A: Type
a: A
f: forest A
IH: length (nodes_forest f) = size_forest fS (length (nodes_forest f)) = S (size_forest f)reflexivity.A: Typelength (nodes_forest (nnil A)) = size_forest (nnil A)A: Typeforall t : tree A, length (nodes t) = size t -> forall f : forest A, length (nodes_forest f) = size_forest f -> length (nodes_forest (ncons A t f)) = size_forest (ncons A t f)A: Type
t: tree A
IHt: length (nodes t) = size t
f: forest A
IHf: length (nodes_forest f) = size_forest flength (nodes_forest (ncons A t f)) = size_forest (ncons A t f)now rewrite length_app, IHt, IHf. Qed.A: Type
t: tree A
IHt: length (nodes t) = size t
f: forest A
IHf: length (nodes_forest f) = size_forest flength (nodes t ++ nodes_forest f) = size t + size_forest f
Indexed inductive types
We now define a type vector : Type -> nat -> Type where the number is the length.
The definition of vectors is not surprising:
Inductive vector (A : Type) : nat -> Type :=
| vnil : vector A 0
| vcons (x : A) (n : nat) (tl : vector A n) : vector A (S n).In vector A n, A is a parameter, because the whole definition is parametric in A, i.e. A never changes.
On the other hand, n is an index, because it changes in the vcons constructor.
There are two ways to ease working with parameters and avoid duplication:
The | notation and sections.
Using the first, we can alternatively define vectors as follows, with less duplication of A:
Inductive vector' (A : Type) | : nat -> Type :=
| vnil' : vector' 0
| vcons' (x : A) (n : nat) (tl : vector' n) : vector' (S n).Sections allow globally fixing variables, such as type variables:
Section fix_A. Variable A : Type. Inductive vector'' : nat -> Type := | vnil'' : vector'' 0 | vcons'' (x : A) (n : nat) (tl : vector'' n) : vector'' (S n).End fix_A.
These variables are added as explicit parameters when the section is closed:
Rocq generates induction principles for vectors, like it does for e.g. lists.
However, to make the induction principle well-typed,
the motive P : vector A n -> Prop
has to take n as an explicit argument:
Try experimenting with writing down the type of an induction principle that looks like this:
forall (A : Type) (forall n : nat) (P : vector A n -> Prop),
<FILLME> -> <FILLME> -> forall (v : vector A n), P v
It won't be possible!
We can, of course, define recursive functions and do proofs over indexed inductive types, like we are used to:
Fixpoint vmap {A B} (f : A -> B) {n} (v : vector A n) : vector B n := match v with | vnil _ => vnil _ | vcons _ a _ tl => vcons _ (f a) _ (vmap f tl) end. Fixpoint vapp {A}{n}{p} (v:vector A n) (w:vector A p): vector A (n+p) := match v with | vnil _ => w | vcons _ a _ tl => vcons _ a _ (vapp tl w) end.A, B: Type
n, p: nat
f: A -> B
v: vector A n
w: vector A pvmap f (vapp v w) = vapp (vmap f v) (vmap f w)A, B: Type
n, p: nat
f: A -> B
v: vector A n
w: vector A pvmap f (vapp v w) = vapp (vmap f v) (vmap f w)A, B: Type
p: nat
f: A -> B
w: vector A pvmap f (vapp (vnil A) w) = vapp (vmap f (vnil A)) (vmap f w)A, B: Type
p: nat
f: A -> B
a: A
n: nat
tl: vector A n
w: vector A p
IHtl: vmap f (vapp tl w) = vapp (vmap f tl) (vmap f w)vmap f (vapp (vcons A a n tl) w) = vapp (vmap f (vcons A a n tl)) (vmap f w)reflexivity.A, B: Type
p: nat
f: A -> B
w: vector A pvmap f (vapp (vnil A) w) = vapp (vmap f (vnil A)) (vmap f w)A, B: Type
p: nat
f: A -> B
a: A
n: nat
tl: vector A n
w: vector A p
IHtl: vmap f (vapp tl w) = vapp (vmap f tl) (vmap f w)vmap f (vapp (vcons A a n tl) w) = vapp (vmap f (vcons A a n tl)) (vmap f w)A, B: Type
p: nat
f: A -> B
a: A
n: nat
tl: vector A n
w: vector A p
IHtl: vmap f (vapp tl w) = vapp (vmap f tl) (vmap f w)vcons B (f a) (n + p) (vmap f (vapp tl w)) = vcons B (f a) (n + p) (vapp (vmap f tl) (vmap f w))reflexivity. Qed.A, B: Type
p: nat
f: A -> B
a: A
n: nat
tl: vector A n
w: vector A p
IHtl: vmap f (vapp tl w) = vapp (vmap f tl) (vmap f w)vcons B (f a) (n + p) (vapp (vmap f tl) (vmap f w)) = vcons B (f a) (n + p) (vapp (vmap f tl) (vmap f w))
Dependent pairs
We will cover dependent pairs on Wednesday.
We define the type of dependent pairs, where the second component can
depend on the first. We often call those Σ-types (hence the name sigT).
Inductive sigT {A} (B : A -> Type) := | existT (a : A) (b : B a) : sigT B. Arguments existT {A B}.
For instance, we can create a type of pairs, where the first component
is always a boolean b, but the second is of type nat when b is
true and of type list nat otherwise.
Definition T := sigT (fun (b : bool) => if b then nat else list nat).We can then create two elements of type T, a pair (true, 17) and a
pair (false, [ 90 ; 4 ; 8 ]). This is something you could not do without
dependent types (so for instance, not in OCaml).
Definition pair1 : T := existT true 17. Definition pair2 : T := existT false [ 90 ; 4 ; 8 ].
Important, you should have a look at the induction principle for dependent pairs.
Similar to how a function of type A * B -> C is the same as A -> B -> C
(which we call currying), we can turn a function of type sigT {A} B -> C to
a (dependent) function of type forall (x : A), B x -> C.
Notice that forall can work for dependent functions and not just for
propositions.
Definition cur {A} {B : A -> Type} {C} (f : sigT B -> C) :
forall (x : A), B x -> C :=
fun a b => f (existT a b).We can perform the opposite transformation.
Definition car {A} {B : A -> Type} {C} (f : forall x, B x -> C) :
sigT B -> C :=
fun p =>
match p with
| existT a b => f a b
end.And now we can prove that they are indeed inverse of each other.
A: Type
B: A -> Type
C: Type
f: sigT B -> C
p: sigT Bcar (cur f) p = f pA: Type
B: A -> Type
C: Type
f: sigT B -> C
p: sigT Bcar (cur f) p = f preflexivity. Qed.A: Type
B: A -> Type
C: Type
f: sigT B -> C
a: A
b: B acar (cur f) (existT a b) = f (existT a b)
The other direction is even simpler, there is nothing to destruct so the proof is direct by reflexivity.
A: Type
B: A -> Type
C: Type
f: forall x : A, B x -> C
a: A
b: B acur (car f) a b = f a breflexivity. Qed.A: Type
B: A -> Type
C: Type
f: forall x : A, B x -> C
a: A
b: B acur (car f) a b = f a b