Course2: Inductive types in Coq
Preliminaries
Church numerals in Coq
A follow-up to exercise 3 of TP1 exercises
n
is represented by λf.λx.(f (f (... (f x))))
where
f
is applied n
times and therefore define a type church to
represent those numbers and some arithmetical functions on this type:
Definition church := ∀ X : Type, (X → X) → X → X.
We first complete exercise 3 of TP1, providing missing definitions of the
following terms:
- two constant zero and one of type church
- a function church_succ of type church→church
- two functions church_plus and church_mult of type church→church→church
- a function church_power
- a test church_iszero
- two functions nat2church : nat → church and church2nat : church → nat converting between Coq inductive definition of nats and the Church encoding
Predecessor and subtraction
- church_pred of type church → church, which associates zero to zero and
Universe constraints
Set Universe Polymorphism.
Defining new Inductive types
Inductive t :=
| C₁ : A₁₁ → ... → A₁ₚ → t
| ...
| Cₙ : Aₙ₁ → ... → Aₙₖ → t
Inductive unit : Set := tt : unit.
Inductive bool :=
| true : bool
| false : bool.
Inductive nat :=
| O : nat
| S : nat → nat.
Print unit.
Print bool.
Print nat.
Positivity constraints
Fail Inductive lam :=
|Fun : (lam → lam) → lam.
On the other hand, one can consider such a type in Ocaml, let us illustrate
the danger of this type, in OCaml:
In Coq, this term dd would be a closed proof of False if these kinds of
inductive types would be accepted. Once again, this is also closely related
with the fact that Coq is strongly normalizing (no infinite computations).
- First, a "lambda-calcul" version:
type lam = Fun : (lam → lam) → lam
let identity = Fun (fun t → t)
let app (Fun f) g = f g
let delta = Fun (fun x → app x x)
let dd = app delta delta - Second, a version producing an infinite computation in any type (hence
could be in Coq's False):
type 'a poly = Poly : ('a poly → 'a) → 'a poly
let app (Poly f) g = f g
let delta = Poly (fun x → app x x)
let dd : 'a = app delta delta
Match
match
, except for little syntactic
differences (⇒ in "branches", final keyword end).
match ... with
| C₁ x₁₁ ... x₁ₚ ⇒ ...
| ...
| Cₙ xₙ₁ ... xₙₖ ⇒ ...
end
Compute
match S (S O) with
| O ⇒ O
| S x ⇒ x
end.
This will reduce to S O (i.e. number 1 with nice pretty-printing).
This computation is actually the definition of pred (natural number
predecessor) from TP0, applied to S (S O) i.e. number 2.
Fix
Print nat.
Require Import ZArith.
Print positive.
Print N.
Print Z.
Nota bene : the "detour" by a specific type positive for strictly
positive numbers allows to ensure that these representations are canonical,
both for N and for Z. In particular, there is only one encoding of zero
in each of these types (N0 in type N, Z0 in type Z).
Coq pairs
Print prod.
Definition fst {A B} (p:A×B) := match p with
| (a,b) ⇒ a
end.
Definition fst' {A B} (p:A×B) :=
let '(a,b) := p in a.
A first example of dependent type
Fixpoint pow n : Type :=
match n with
| 0 ⇒ unit
| S n ⇒ (nat × (pow n))%type
end.
The option type
Print option.
Print list.
Require Import List.
Import ListNotations.
Check (3 :: 4 :: []).
Fixpoint length {A} (l : list A) :=
match l with
| [] ⇒ 0
| x :: l ⇒ S (length l)
end.
Trees in Coq
Inductive tree :=
| leaf
| node : nat → tree → tree → tree.
Exercise 9: Binary trees with distinct internal and external nodes.
Exercise 10: Lists alternating elements of two types.
Inductive ord :=
| zero : ord
| succ : ord → ord
| lim : (nat→ord) → ord.
Note that this inductive type does satisfy the strict positivity
constraint: constructor lim has an argument of type nat→ord,
where ord appears indeed on the right. Having instead
lim:(ord→nat)->ord would be refused by Coq.
We can plunge in this type the usual natural numbers of type nat.
For instance via a mixed addition add : ord → nat → ord :
Fixpoint add a n :=
match n with
| 0 ⇒ a
| S n ⇒ succ (add a n)
end.
Definition nat2ord n := add zero n.
match n with
| 0 ⇒ a
| S n ⇒ succ (add a n)
end.
Definition nat2ord n := add zero n.
Now, we could use constructor lim and this add
function to go beyond the usual numbers.
Definition omega := lim (add zero).
Definition deuxomega := lim (add omega).
Fixpoint nomega n :=
match n with
| 0 ⇒ zero
| S n ⇒ lim (add (nomega n))
end.
Definition omegadeux := lim nomega.
Be careful, the standard equality of Coq is not very
meaningful on these ordinals, since it is purely syntactic.
For instance add zero and add (succ zero) are two different
sequences (numbers starting at 0 vs. numbers starting at 1).
So Coq will allow proving that lim (add zero) ≠
lim (add (succ zero)) (where ≠ is the negation of the
logical equality =). But we usually consider the limits of
these two sequences to be two possible descriptions of omega,
the first infinite ordinal. We would then have to define and
use a specific equality on ord, actually an equivalence
relation (we also call that a setoid equality).
Let us encode a type of trees made of nodes having a natural
number on them, and then an arbitrary number of subtrees,
not just two like last week's tree.
Trees of variable arity
Inductive ntree :=
| Node : nat → list ntree → ntree.
Note that this inductive type need not have a "base"
constructor like O for nat or leaf for last week tree.
Instead, we could use Node n [] for representing a leaf.
An example of program over this type:
Require Import List.
Import ListNotations.
Addition of all elements of a list of natural numbers
Fixpoint sum (l:list nat) : nat :=
match l with
| [] ⇒ 0
| x::l ⇒ x + sum l
end.
match l with
| [] ⇒ 0
| x::l ⇒ x + sum l
end.
List.map : iterating a function over all elements of a list
Check List.map.
How many nodes in a ntree ?
Fixpoint ntree_size t :=
match t with
| Node _ ts ⇒ 1 + sum (List.map ntree_size ts)
end.
match t with
| Node _ ts ⇒ 1 + sum (List.map ntree_size ts)
end.
Why is this function ntree_size accepted as strictly decreasing?
Indeed ts is a subpart of t, but we are not launching the recursive
call on ts itself. Fortunately, here Coq is clever enough to enter
the code of List.map and see that ntree_size will be launched on
subparts of ts, and hence transitively subparts of t. But that trick
only works for a specific implementation of List.map (check with your
own during the practical session).
Is the Ackermann function structurally decreasing ?
No if we consider only one argument, as Coq does. Indeed, neither
n nor m (taken separately) ensures a strict decrease. But there
is a trick (quite standard now) : we could separate this function
into an external fixpoint (decreasing on n) and an internal
fixpoint (decreasing on m), and hence emulate a lexicographic
ordering on the arguments. The inner fixpoint uses the fix
syntax :
Internal recursive function : fix
- ack 0 m = m+1
- ack (n+1) 0 = ack n 1
- ack (n+1) (m+1) = ack n (ack (n+1) m)
Fixpoint ack n :=
match n with
| 0 ⇒ S
| S n ⇒
fix ack_Sn m :=
match m with
| 0 ⇒ ack n 1
| S m ⇒ ack n (ack_Sn m)
end
end.
Compute ack 3 5.
Induction Principles
Check nat_rect.
Print nat_rect.
Deep inside this nat_rect, one finds a fix and a match,
and this recursion and case analysis is just as generic as it could
be for nat :
we could program on nat without any more Fixpoint nor fix
nor match, just with nat_rect! For instance:
Definition pred n : nat := nat_rect _ 0 (fun n h ⇒ n) n.
Definition add n m : nat := nat_rect _ m (fun _ h ⇒ S h) n.
In these two cases, the "predicate" P needed by nat_rect
(its first argument _) is actually fun _ ⇒ nat, meaning that
we are using nat_rect in a non-dependent manner (more on that in
a forthcoming session).
Example of Pos.peano_rect and N.peano_rect (mentioned in the
solution of TD1) : we could manually "hijack" the (binary) recursion
on type positive for building a peano-like induction principle
following (apparently) a unary recursion. Check in particular that
Pos.peano_rect is indeed structurally decreasing.
Pseudo Induction Principles
Require Import PArith NArith.
Check Pos.peano_rect.
Check N.peano_rect.
Print Pos.peano_rect.
Print N.peano_rect.
A cleaned-up version of peano_rect :
Open Scope positive.
Fixpoint peano_rect
(P : positive → Type)
(a : P 1)
(f : ∀ {p}, P p → P (Pos.succ p))
(p : positive) : P p :=
let Q := fun q ⇒ P (q~0) in
match p with
| q~1 ⇒ f (peano_rect Q (f a) (fun _ h ⇒ f (f h)) q)
| q~0 ⇒ peano_rect Q (f a) (fun _ h ⇒ f (f h)) q
| 1 ⇒ a
end.
The inner call to peano_rect builds P (q~0) by starting at P 2
(justified by f a) and going up q times two steps by two steps
(cf fun _ h ⇒ f (f h)).
Practice: TP 2
Recursive definitions
Exercise 6 : Fibonacci
- Define a function fib such that fib 0 = 0, fib 1 = 1 then fib (n+2) = fib (n+1) + fib n. (you may use a as keyword to name some subpart of the match pattern ("motif" en français)).
- Define an optimized version of fib that computes faster that the previous one by using Coq pairs.
- Same question with just natural numbers, no pairs. Hint: use a special recursive style called "tail recursion".
- Load the library of binary numbers via Require Import NArith. Adapt you previous functions for them now to have type nat → N. What impact does it have on efficiency ? Is it possible to simply obtain functions of type N → N ?
Exercise 7 : Fibonacci though matrices
- Define a type of 2x2 matrices of numbers (for instance via a quadruple).
- Define the multiplication and the power of these matrices. Hint: the power may use an argument of type positive.
- Define a fibonacci function through power of the following matrix:
1 1 1 0
Exercise 8 : Fibonacci decomposition of numbers
Require Import List.
Import ListNotations.
- Write a function fib_inv : nat → nat such that if fib_inv n = k then fib k ≤ n < fib (k+1).
- Write a function fib_sum : list nat → nat such that fib_sum [k_1;...;k_p] = fib k_1 + ... + fib k_p.
- Write a function decomp : nat → list nat such that fib_sum (decomp n) = n and decomp n does not contain 0 nor 1 nor any redundancy nor any successive numbers.
- (Optional) Write a function normalise : list nat → list nat which receives a decomposition without 0 nor 1 nor redundancy, but may contains successive numbers, and builds a decomposition without 0 nor 1 nor redundancy nor successive numbers. You might assume here that the input list of this function is sorted in the way you prefer.
Some inductive types
Exercise 9: Binary trees with distinct internal and external nodes.
Exercise 10: Lists alternating elements of two types.
In the exercises below, you should have loaded the
following Coq libraries:
Require Import Bool Arith List.
Import ListNotations.
Exercise 11: Classical exercises on lists
- length
- concatenate (app in Coq, infix notation ++)
- rev (for reverse, a.k.a mirror)
- map : ∀ {A B}, (A→B)-> list A → list B
- filter : ∀ {A}, (A→bool) → list A → list A
- at least one fold function, either fold_right or fold_left
- seq : nat → nat → list nat, such that seq a n = [a; a+1; ... a+n-1]
Exercise 12: Some executable predicates on lists
- forallb : ∀ {A}, (A→bool) → list A → bool.
- increasing which tests whether a list of numbers is strictly increasing.
- delta which tests whether two successive numbers of the list are always apart by at least k.
Exercise 13: Mergesort
- Write a split function which dispatch the elements of a list into two lists of half the size (or almost). It is not important whether an element ends in the first or second list. In particular, concatenating the two final lists will not necessary produce back the initial one, just a permutation of it.
- Write a merge function which merges two sorted lists into a
new sorted list containing all elements of the initial ones.
This can be done with structural recursion thanks to a inner
fix (see ack in the session 3 of the course).
- Write a mergesort function based on the former functions.
Exercise 14: Powerset
This page has been generated by coqdoc