Built with Alectryon, running Coq+SerAPI v8.19.0+0.19.3. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑ Ctrl+↓ to navigate, Ctrl+🖱️ to focus. On Mac, use instead of Ctrl.

Lecture 4: Lists and Modules

Lists

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

= 3 : nat

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 A

length (append l1 l2) = length l1 + length l2
A: Type
l1, l2: list A

length (append l1 l2) = length l1 + length l2
induction l1; simpl; congruence. Qed.

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 A

map (compose f g) l = map g (map f l)
A, B, C: Type
f: A -> B
g: B -> C
l: list A

map (compose f g) l = map g (map f l)
A, B, C: Type
f: A -> B
g: B -> C

map (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

map (compose f g) [ ] = map g (map f [ ])
reflexivity.
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)
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)
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: Type
l: list A

fold cons [ ] l = l
A: Type
l: list A

fold cons [ ] l = l
induction l; simpl; congruence. Qed.

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.

= Some 3 : option nat

Compute the 3rd element of l1.

= None : option nat
[Loading ML file ring_plugin.cmxs (using legacy method) ... done]
[Loading ML file zify_plugin.cmxs (using legacy method) ... done]
[Loading ML file micromega_plugin.cmxs (using legacy method) ... done]

Let's prove a lemma about nth.

A: Type
l: list A
n: nat

nth l n = None <-> length l <= n
A: Type
l: list A
n: nat

nth l n = None <-> length l <= n
A: Type
l: list A

forall n : nat, nth l n = None <-> length l <= n
A: Type

None = None <-> 0 <= 0
A: Type
n: nat
None = None <-> 0 <= S n
A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
Some a = None <-> S (length l) <= 0
A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: nat
nth l n = None <-> S (length l) <= S n
A: Type

None = None <-> 0 <= 0
split; [lia|auto].
A: Type
n: nat

None = None <-> 0 <= S n
split; [lia|auto].
A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n

Some a = None <-> S (length l) <= 0
split; [congruence|lia].
A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: nat

nth l n = None <-> S (length l) <= S n
A: Type
a: A
l: list A
IHl: forall n : nat, nth l n = None <-> length l <= n
n: nat

length l <= n <-> S (length l) <= S n
split; lia. Qed.

Modules

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) z

forall x y z : nat, op x (op y z) = op (op x y) z
x, y, z: nat

op x (op y z) = op (op x y) z
x, y, z: nat

x + (y + z) = x + y + z
lia. Qed.

forall x : nat, op id x = x

forall x : nat, op id x = x
x: nat

op id x = x
x: nat

0 + x = x
lia. Qed.

forall x : nat, op x id = x

forall x : nat, op x id = x
x: nat

op x id = x
x: nat

x + 0 = x
lia. Qed. End NatMonoid.

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) z

forall x y z : t, op x (op y z) = op (op x y) z
x, y, z: t

op x (op y z) = op (op x y) z
x, y, z: t

append x (append y z) = append (append x y) z
a: nat
x: list nat
y, z: t
IHx: append x (append y z) = append (append x y) z

a :: append x (append y z) = a :: append (append x y) z
a: nat
x: list nat
y, z: t
IHx: append x (append y z) = append (append x y) z

a :: append (append x y) z = a :: append (append x y) z
auto. Qed.

forall x : t, op id x = x

forall x : t, op id x = x
reflexivity. Qed.

forall x : t, op x id = x

forall x : t, op x id = x
x: t

op x id = x
x: t

append x id = x
a: nat
x: list nat
IHx: append x id = x

a :: append x id = a :: x
a: nat
x: list nat
IHx: append x id = x

a :: x = a :: x
auto. Qed. End NatListMonoid.

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) z

forall x y z : t, op x (op y z) = op (op x y) z
x, y, z: t

op x (op y z) = op (op x y) z
x, 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))
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))
reflexivity. Qed. (* Left identity law *)

forall x : t, op id x = x

forall x : t, op id x = x
x: t

op id x = x
x: t

(M1.op (fst (M1.id, M2.id)) (fst x), M2.op (snd (M1.id, M2.id)) (snd x)) = x
x: t

(fst x, M2.op (snd (M1.id, M2.id)) (snd x)) = x
x: t

(fst x, snd x) = x
t0: M1.t
t1: M2.t

(fst (t0, t1), snd (t0, t1)) = (t0, t1)
reflexivity. Qed. (* Right identity law *)

forall x : t, op x id = x

forall x : t, op x id = x
x: t

op x id = x
x: t

(M1.op (fst x) (fst (M1.id, M2.id)), M2.op (snd x) (snd (M1.id, M2.id))) = x
x: t

(fst x, M2.op (snd x) (snd (M1.id, M2.id))) = x
x: t

(fst x, snd x) = x
t0: M1.t
t1: M2.t

(fst (t0, t1), snd (t0, t1)) = (t0, t1)
reflexivity. Qed. End ProductMonoid.

Example of using the product monoid.

Module OurMonoid := ProductMonoid NatMonoid NatListMonoid.

Definition elem : OurMonoid.t := (2, [1;2]).

= (4, [1; 2; 1; 2]) : OurMonoid.t

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.id

M.op M.id M.id = M.id
apply M.id_right. Qed. (* We can import M to avoid having to type `M.abc` *) Import M.

op id id = id

op id id = id
apply id_right. Qed. End MonoidLemmas.

Let's apply our generic lemmas to OurMonoid.

Module OurLemmas := MonoidLemmas OurMonoid.


OurMonoid.op OurMonoid.id OurMonoid.id = OurMonoid.id

OurMonoid.op OurMonoid.id OurMonoid.id = OurMonoid.id
apply OurLemmas.id_id. Qed.

You can also import at the top level.

Import OurMonoid.
Import OurLemmas.


op id id = id

op id id = id
apply id_id. Qed.