Course1: Introduction to Functional Programming in Coq
Preliminaries
- Definition : binds some name with a (non-recursive) Coq term;
- Fixpoint : same, for a recursive definition;
- Inductive : creates a new inductive type and its constructors;
- Check : displays the type of a Coq term;
- Print : displays the body of a definition or the details of an inductive type;
- Compute : reduces a term (i.e. determines its normal form) and prints it.
.
is mandatory to end each Coq phrase.
Coq functional core
- variables
- function abstraction :
λx.t
(written fun x ⇒ t in Coq) - function application :
t u
(same in Coq).
-
t(u)
(regular math style) or -
(t)u
(Krivine style).
Typing
Fail Check 1 + true.
Fail Check negb 42.
- If x:T is in Γ then Γ ⊢ x:T
- If Γ+x:τ ⊢ t:σ then Γ ⊢ (λx.t):(τ→σ)
- If Γ ⊢ t:(τ→σ) and Γ ⊢ u:τ then Γ ⊢ (t u) : σ
__________(Var) if x:T in Γ Γ ⊢ x:T Γ+x:τ ⊢ t:σ __________________(Lam) Γ ⊢ (λx.t):(τ→σ)and
Γ ⊢ t:(τ→σ) Γ ⊢ u:τ ____________________________(App) Γ ⊢ (t u) : σ
Computations
(λx.t) u
gives t{x:=u}
(for some reasonable definition
of substitution).
Δ Δ
with Δ = λx.(x x)
.
From that, fixpoint combinators can be created and used for
defining recursive functions in raw λ-calculus (but that's quite
tricky, see the Y
combinator for instance).
- The type is preserved during reduction (property known as subject reduction).
- Strong normalization : a well-typed term may not have infinite
reduction (hence
Δ Δ
cannot be well-typed). - Strong normalization + confluence implies that reducing a well-typed
term
t
as long as possible in any possible manner always ends on the unique normal formt₀
oft
.
Δ Δ
or fixpoint combinators in
Coq due to typing. Instead, Coq provides a native way to define
recursive functions (see later). Unlike OCaml, this is not a general
recursivity, but only recursive functions that obviously terminates
(for some criterion). This way, Coq is strongly normalizing. Moreover
no trivial closed proofs of False could be built (see later). But
this also implies that Coq is not Turing-complete. We will see how
the programmer may cope with that.
Some more details on Coq types
- Type is the most general sort in Coq.
- Prop is used to express logical statements, we will encounter it in the second half of this course.
- Set is a (deprecated) alias for a Type of the lowest level
Type₀ : Type₁ : Type₂ : ...
- If Γ ⊢ A:Type and Γ+x:A ⊢ B:Type then Γ ⊢ (∀ x:A,B) : Type
- If Γ ⊢ A: Type(i) and Γ+x:A ⊢ B:Type(j) then Γ ⊢ (∀ x:A,B) : Type(max(i,j))
Γ ⊢ A:Type(i) Γ+x:A ⊢ B:Type(j) ________________________________________(Prod-Type) Γ ⊢ (forall x:A,B) : Type(max(i,j))
- If Γ+x:A ⊢ t:B then Γ ⊢ (fun x ⇒ t):(∀ x:A, B)
- If Γ ⊢ t:(∀ x:A,B) and Γ ⊢ u:A then Γ ⊢ (t u) : B{x:=u}
Γ+x:A ⊢ t:B __________________________________(Lam) Γ ⊢ (fun x => t):(forall x:A, B) Γ ⊢ t:(forall x:A,B) Γ ⊢ u:A _____________________________________(App) Γ ⊢ (t u) : B{x:=u}
Coq constants, global definitions and declarations.
Global definitions.
Definition c : t := u.
Definition id_1 : ∀ X, X→X := fun (X:Type)(x:X) ⇒ x.
Definition id_2 : ∀ X, X→X := fun X x ⇒ x.
type inference in fun
Definition id_3 := fun (X:Type)(x:X) ⇒ x.
inference of the constant type
Definition id_4 (X:Type)(x:X) : X := x.
constant with arguments after the constant name
Definition id_5 (X:Type)(x:X) := x.
same, without explicit constant type
Definition id_6 X (x:X) := x.
default sort is Type
Coq also provides a
let ... in
syntax, let x := t in u, for local definitions:
Definition test :=
let id := fun X (x:X) ⇒ x in
let id2 X := id (X→X) (id X) in
let id4 X := id2 (X→X) (id2 X) in
id4.
A let x := t in u behaves almost like ((fun x⇒u) t).
Once again, a reduction rule (ζ, zeta) allows to discard
this local abbreviation by replacing everywhere x by t in u.
A global declaration is introduced with the syntax Parameter u : T
or Parameters u v : T when several variables are defined at the
same time.
No value is assigned with the declared identifiers and no value will
ever be. Therefore, this feature will mostly interest us when
performing reasoning and formal proofs in Coq, later in the semester.
Global declarations.
Parameter max_int : nat.
Parameters screen_width screen_height : nat.
A definition can make use of global parameters:
Definition screen_area := screen_width × screen_height.
Check screen_area.
Print screen_area.
Section mechanism and local variables
Section mysection.
...
End mysection.
Section Identity.
Variable X: Set.
Definition identity := fun x: X ⇒ x.
Check identity.
Print identity.
End Identity.
Check identity.
Print identity.
Local definition
Section test_def.
Variables x y: nat.
Let a := x + y.
Let b := x × y.
Definition c := 2 × a + 3 × b.
Print a.
Print c.
End test_def.
Fail Print a.
Print c.
Exercise 1 : Function composition
Exercise 2 : Boolean ersatz
- a type mybool : Type;
- two constants mytrue and myfalse of type mybool;
- a function myif : ∀ A, mybool → A → A → A such that myif mytrue x y computes to x and myif myfalse x y computes to y.
Exercise 3 : Church numerals
n
is represented by λf.λx.(f (f (... (f x))))
where
f
is applied n
times.
- a type church : Type
- two constant zero and one of type church
- a function succ of type church→church
- two functions plus and mult of type church→church→church
- a function power
- a test iszero
First data types : Boolean and natural numbers.
bool
- negation : negb
- logical "and" : andb (infix notation &&, available after doing Open Scope bool_scope)
- logical "or" : orb (infix notation ||, available after doing Open Scope bool_scope)
Exercise 4 : Booleans
- Write a function checktauto : (bool→bool)->bool which tests whether a Boolean unary function always answers true.
- Same for checktauto2 and checktauto3 for Boolean functions expecting 2, then 3 arguments. This can be done by enumerating all cases, but there is a clever way to proceed (for instance by re-using checktauto.
- Check whether fun a b c ⇒ a || b || c || negb (a && b) || negb (a && c) is a tautology. Note : the command Open Scope bool_scope. activates notations || and && (respectively for functions orb and andb).
- Define some functions behaving like Coq standard functions negb and orb and andb.
nat
Require Import Arith.
Some operations defined on nat:
We will see later how to perform efficient arithmetical operations
(binary encoding) and how to handle negative numbers (type Z).
- addition +,
- multiplication ×,
- euclidean division /,
- modulo x mod y.
- boolean comparisons of numbers : x =? y or x <? y or x <=? y.
Recursivity
Fixpoint factorial (n:nat) :=
match n with
| 0 ⇒ 1
| S m ⇒ (factorial m) × n
end.
This criterion is quite restrictive. For instance here, replacing
factorial m with factorial (n-1) est refused, even though we
could prove later that m = (n-1) in this case.
Fail Fixpoint failctorial (n:nat) :=
match n with
| 0 ⇒ 1
| S m ⇒ (failctorial (n-1)) × n
end.
Exercise 5 : Usual functions on natural numbers.
- addition
- multiplication
- subtraction
- power
- gcd
- AP(0,n) = n+1
- AP(m+1, 0) = AP(m, 1)
- AP(m+1, n+1) = AP(m, AP(m+1, n)).
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.
First-class function and partial application
Check Init.Nat.add.
Definition plus3 := Init.Nat.add 3.
Check plus3.
Definition apply := fun A B (x:A) (f: A → B) ⇒ f x.
Check apply.
Definition k := (fun (g : nat → nat → nat) h (x y z : nat) ⇒ g x (S (h y z))).
Check k.
General recursivity and logical consistency
Print False.
Here closed means without variables nor axioms in the typing
environment. Without even knowing how False is defined in
Fixpoint loop (n:nat) : False := loop n
Definition boom : False := loop 0.
Obviously such a definition is rejected by
let rec loop (n:int) : 'a = loop n
let any : 'a = loop 0
Similarly,
Once again, an unrestricted general recursion would break this
property. For example:
[Fixpoint flipflop (b:bool) := negb (flipflop b).
Definition alien : flipflop true.]
If flipflop were accepted by Coq (it is not!), we would have the
equation flipflop true = negb (flipflop true), and hence
alien = negb alien. This alien cannot hence be true, nor false.
Coq
,
a fully general recursion would give us such a proof.
Reminder : in Coq
there is no syntactic distinction between
proofs and programs.
Fixpoint loop (n:nat) : False := loop n
Definition boom : False := loop 0.
Coq
. Here is the OCaml
equivalent of this code (no question of logical soundness in this case):
let rec loop (n:int) : 'a = loop n
let any : 'a = loop 0
Coq
relies crucially on the property that a closed term
in an inductive type (see next section) will evaluate (we say also
"reduce") to one of the constructors of this type, followed by the
right number of arguments. This allows to derive properties such as :
- all boolean expression is either equal to true or to false,
- all natural number of type nat is either zero or a successor,
- etc.
Fail Fixpoint flipflop (b:bool) := negb (flipflop b).
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
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
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.
Practice: TP 1
Functions and only that
Exercise 1 : Function composition
Exercise 2 : Boolean ersatz
- a type mybool : Type;
- two constants mytrue and myfalse of type mybool;
- a function myif : ∀ A, mybool → A → A → A such that myif mytrue x y computes to x and myif myfalse x y computes to y.
Exercise 3 : Church numerals
n
is represented by λf.λx.(f (f (... (f x))))
where
f
is applied n
times.
- a type church : Type
- 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
Base types
Exercise 4 : Booleans
- Write a function checktauto : (bool→bool)->bool which tests whether a Boolean unary function always answers true.
- Same for checktauto2 and checktauto3 for Boolean functions expecting 2, then 3 arguments. This can be done by enumerating all cases, but there is a clever way to proceed (for instance by re-using checktauto.
- Check whether fun a b c ⇒ a || b || c || negb (a && b) || negb (a && c) is a tautology. Note : the command Open Scope bool_scope. activates notations || and && (respectively for functions orb and andb).
- Define some functions behaving like Coq standard functions negb and orb and andb.
Exercise 5 : Usual functions on natural numbers.
- addition
- multiplication
- subtraction
- power
- gcd
- AP(0,n) = n+1
- AP(m+1, 0) = AP(m, 1)
- AP(m+1, n+1) = AP(m, AP(m+1, n)).
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.
This page has been generated by coqdoc