Definition of the list data type
Inductive list (A : Type) : Type :=
| nil : list A
| cons : A -> list A -> list A.
The list type is parameterized by the type A
of elements in the list.
We can define some lists:
Definition l1 := cons nat 1 (cons nat 2 (cons nat 3 (nil nat))).
As you can see, it is a bit cumbersome to pass the type nat
into the list constructors explicitly.
Coq has a feature called Implicit Arguments
that allows us to omit the type argument when the type is clear from the context.
Arguments nil {_}. (* Make the first argument of `nil` implicit *) Arguments cons {_} _ _. (* Make the first argument of `cons` implicit and leave the other two explicit *)
Now we can define l1
without explicitly passing the type argument:
Definition l1' := cons 1 (cons 2 (cons 3 nil)).
In fact, Coq's notation system is quite powerful:
Notation "x :: l" := (cons x l) (at level 60, right associativity). Notation "[ ]" := nil. Notation "[ x ; .. ; y ]" := (cons x .. (cons y nil) ..). Definition l1'' := 1 :: 2 :: 3 :: nil. Definition l1''' := [1; 2; 3].
We can define some useful functions on lists.
The following function length
returns the number of elements in a list.
The first argument {A : Type}
is the type of the elements in the list, and the braces {}
are used to make the argument implicit.
Fixpoint length {A : Type} (l : list A) : nat :=
match l with
| [] => 0
| h :: t => 1 + length t
end.
Compute the length of l1
The following function append
concatenates two lists.
Fixpoint append {A : Type} (l1 l2 : list A) : list A :=
match l1 with
| [] => l2
| h :: t => h :: append t l2
end.
A lemma about the length of the appended list. In Coq, we can not only do induction on natural numbers, but on any inductive type.
A: Type
l1, l2: list Alength (append l1 l2) = length l1 + length l2induction l1; simpl; congruence. Qed.A: Type
l1, l2: list Alength (append l1 l2) = length l1 + length l2
A couple of higher-order functions.
Fixpoint map {A B : Type} (f : A -> B) (l : list A) : list B := match l with | [] => [] | h :: t => f h :: map f t end. Definition compose {A B C : Type} (f : A -> B) (g : B -> C) (x : A) : C := g (f x).A, B, C: Type
f: A -> B
g: B -> C
l: list Amap (compose f g) l = map g (map f l)A, B, C: Type
f: A -> B
g: B -> C
l: list Amap (compose f g) l = map g (map f l)A, B, C: Type
f: A -> B
g: B -> Cmap (compose f g) [ ] = map g (map f [ ])A, B, C: Type
f: A -> B
g: B -> C
a: A
l: list A
IHl: map (compose f g) l = map g (map f l)map (compose f g) (a :: l) = map g (map f (a :: l))reflexivity.A, B, C: Type
f: A -> B
g: B -> Cmap (compose f g) [ ] = map g (map f [ ])A, B, C: Type
f: A -> B
g: B -> C
a: A
l: list A
IHl: map (compose f g) l = map g (map f l)map (compose f g) (a :: l) = map g (map f (a :: l))A, B, C: Type
f: A -> B
g: B -> C
a: A
l: list A
IHl: map (compose f g) l = map g (map f l)compose f g a :: map (compose f g) l = g (f a) :: map g (map f l)reflexivity. Qed. Fixpoint fold {A B : Type} (f : A -> B -> B) (z : B) (l : list A) : B := match l with | [] => z | h :: t => f h (fold f z t) end.A, B, C: Type
f: A -> B
g: B -> C
a: A
l: list A
IHl: map (compose f g) l = map g (map f l)compose f g a :: map g (map f l) = g (f a) :: map g (map f l)A: Type
l: list Afold cons [ ] l = linduction l; simpl; congruence. Qed.A: Type
l: list Afold cons [ ] l = l
A function to look up the nth element of a list.
Inductive option (A : Type) : Type := | Some : A -> option A | None : option A. Arguments Some {_} _. Arguments None {_}. Fixpoint nth {A : Type} (l : list A) (n : nat) : option A := match n, l with | 0, h :: _ => Some h | S n', _ :: t => nth t n' | _, [] => None end.
Compute the 2nd element of l1
.
Compute the 3rd element of l1
.
Let's prove a lemma about nth
.
A: Type
l: list A
n: natnth l n = None <-> length l <= nA: Type
l: list A
n: natnth l n = None <-> length l <= nA: Type
l: list Aforall n : nat, nth l n = None <-> length l <= nA: TypeNone = None <-> 0 <= 0A: Type
n: natNone = None <-> 0 <= S nA: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= nSome a = None <-> S (length l) <= 0A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: natnth l n = None <-> S (length l) <= S nsplit; [lia|auto].A: TypeNone = None <-> 0 <= 0split; [lia|auto].A: Type
n: natNone = None <-> 0 <= S nsplit; [congruence|lia].A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= nSome a = None <-> S (length l) <= 0A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: natnth l n = None <-> S (length l) <= S nsplit; lia. Qed.A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: natlength l <= n <-> S (length l) <= S n
Coq has modules to structure your definitions and proofs. We can define a module type for monoids (a type with an associative operation and a left and right identity) as follows:
Define the module type for a monoid.
Module Type Monoid. (* The carrier type *) Parameter t : Type. (* The binary operation *) Parameter op : t -> t -> t. (* The identity element *) Parameter id : t. (* The associativity axiom *) Axiom op_assoc : forall x y z : t, op x (op y z) = op (op x y) z. (* The identity element laws *) Axiom id_left : forall x : t, op id x = x. Axiom id_right : forall x : t, op x id = x. End Monoid.
Instance of monoid for nat with addition.
Module NatMonoid <: Monoid. Definition t := nat. Definition op := Nat.add. Definition id := 0.forall x y z : nat, op x (op y z) = op (op x y) zforall x y z : nat, op x (op y z) = op (op x y) zx, y, z: natop x (op y z) = op (op x y) zlia. Qed.x, y, z: natx + (y + z) = x + y + zforall x : nat, op id x = xforall x : nat, op id x = xx: natop id x = xlia. Qed.x: nat0 + x = xforall x : nat, op x id = xforall x : nat, op x id = xx: natop x id = xlia. Qed. End NatMonoid.x: natx + 0 = x
Instance of monoid for list with concatenation.
Module NatListMonoid <: Monoid. Definition t := list nat. Definition op := @append nat. Definition id := @nil nat.forall x y z : t, op x (op y z) = op (op x y) zforall x y z : t, op x (op y z) = op (op x y) zx, y, z: top x (op y z) = op (op x y) zx, y, z: tappend x (append y z) = append (append x y) za: nat
x: list nat
y, z: t
IHx: append x (append y z) = append (append x y) za :: append x (append y z) = a :: append (append x y) zauto. Qed.a: nat
x: list nat
y, z: t
IHx: append x (append y z) = append (append x y) za :: append (append x y) z = a :: append (append x y) zforall x : t, op id x = xreflexivity. Qed.forall x : t, op id x = xforall x : t, op x id = xforall x : t, op x id = xx: top x id = xx: tappend x id = xa: nat
x: list nat
IHx: append x id = xa :: append x id = a :: xauto. Qed. End NatListMonoid.a: nat
x: list nat
IHx: append x id = xa :: x = a :: x
Functor that takes two monoids and returns their product monoid.
Module ProductMonoid (M1 M2 : Monoid) <: Monoid. (* The carrier type is a pair of the carrier types of the two monoids *) Definition t := (M1.t * M2.t)%type. (* The binary operation is defined component-wise *) Definition op (x y : t) : t := (M1.op (fst x) (fst y), M2.op (snd x) (snd y)). (* The identity element is the pair of the identity elements of the two monoids *) Definition id : t := (M1.id, M2.id). (* Associativity of the operation *)forall x y z : t, op x (op y z) = op (op x y) zforall x y z : t, op x (op y z) = op (op x y) zx, y, z: top x (op y z) = op (op x y) zx, y, z: t(M1.op (fst x) (fst (M1.op (fst y) (fst z), M2.op (snd y) (snd z))), M2.op (snd x) (snd (M1.op (fst y) (fst z), M2.op (snd y) (snd z)))) = (M1.op (fst (M1.op (fst x) (fst y), M2.op (snd x) (snd y))) (fst z), M2.op (snd (M1.op (fst x) (fst y), M2.op (snd x) (snd y))) (snd z))x, y, z: t(M1.op (fst x) (M1.op (fst y) (fst z)), M2.op (snd x) (M2.op (snd y) (snd z))) = (M1.op (M1.op (fst x) (fst y)) (fst z), M2.op (M2.op (snd x) (snd y)) (snd z))x, y, z: t(M1.op (M1.op (fst x) (fst y)) (fst z), M2.op (snd x) (M2.op (snd y) (snd z))) = (M1.op (M1.op (fst x) (fst y)) (fst z), M2.op (M2.op (snd x) (snd y)) (snd z))reflexivity. Qed. (* Left identity law *)x, y, z: t(M1.op (M1.op (fst x) (fst y)) (fst z), M2.op (M2.op (snd x) (snd y)) (snd z)) = (M1.op (M1.op (fst x) (fst y)) (fst z), M2.op (M2.op (snd x) (snd y)) (snd z))forall x : t, op id x = xforall x : t, op id x = xx: top id x = xx: t(M1.op (fst (M1.id, M2.id)) (fst x), M2.op (snd (M1.id, M2.id)) (snd x)) = xx: t(fst x, M2.op (snd (M1.id, M2.id)) (snd x)) = xx: t(fst x, snd x) = xreflexivity. Qed. (* Right identity law *)t0: M1.t
t1: M2.t(fst (t0, t1), snd (t0, t1)) = (t0, t1)forall x : t, op x id = xforall x : t, op x id = xx: top x id = xx: t(M1.op (fst x) (fst (M1.id, M2.id)), M2.op (snd x) (snd (M1.id, M2.id))) = xx: t(fst x, M2.op (snd x) (snd (M1.id, M2.id))) = xx: t(fst x, snd x) = xreflexivity. Qed. End ProductMonoid.t0: M1.t
t1: M2.t(fst (t0, t1), snd (t0, t1)) = (t0, t1)
Example of using the product monoid.
Module OurMonoid := ProductMonoid NatMonoid NatListMonoid. Definition elem : OurMonoid.t := (2, [1;2]).
We can use Modules to obtain lemmas that are generic over the monoid.
Module MonoidLemmas (M : Monoid). (* Derived lemmas for monoid aren't very interesting, so we just have this silly lemma for illustration. *)M.op M.id M.id = M.idapply M.id_right. Qed. (* We can import M to avoid having to type `M.abc` *) Import M.M.op M.id M.id = M.idop id id = idapply id_right. Qed. End MonoidLemmas.op id id = id
Let's apply our generic lemmas to OurMonoid.
Module OurLemmas := MonoidLemmas OurMonoid.OurMonoid.op OurMonoid.id OurMonoid.id = OurMonoid.idapply OurLemmas.id_id. Qed.OurMonoid.op OurMonoid.id OurMonoid.id = OurMonoid.id
You can also import at the top level.
Import OurMonoid. Import OurLemmas.op id id = idapply id_id. Qed.op id id = id