Course1: Introduction to Functional Programming in Coq

Preliminaries

The Coq file supporting today's course is available at
https://gitlab.math.univ-paris-diderot.fr/saurin/coq-lmfi-2023/-/blob/main/TP/TP1.v.
For instructions on how to install, launch and where to find documentation about Coq, please refer to TP0.
Today, we will study in more details the basic notions introduction in TP0. More specifically, we will continue getting you acquainted with the following basic commands:
  • 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.
A dot . is mandatory to end each Coq phrase.


Coq functional core

Coq directly derives from the lambda -calculus (invented by Alonzo Church in the 1930s) and its three basic constructions:
  • variables
  • function abstraction : λx.t (written fun x t in Coq)
  • function application : t u (same in Coq).
In other communities (and in the Proof theory related course of the first and second semester in particular), the function application may be written:
  • t(u) (regular math style) or
  • (t)u (Krivine style).
Here in Coq and many other functional languages we favor a light style, with parenthesis only in case of ambiguity : f (x+1) isn't f x + 1 (which is rather (f x) + 1).
For instance x y z is a shortcut for ((x y) z) : one function x applied to two successive arguments y, then z. And (x (y z)) is quite different : y will be a function receiving z, and the result is given as a parameter to function x.
Important rule of thumb : in function applications, put parenthesis around every argument that is not a variable or a constant.
Other important shortcut : fun x y t is a short for fun x fun y t. Actually, a binary function is a unary function returning a unary function ! More generally, fun x1 ... xk t is a short for fun x1 ... fun xk t.


Typing

By default, lambda-calculus is said raw or pure or untyped : you can try applying anything to anything and look if it breaks (and it may: there are looping programs which never return in the lambda-calculus).
There are actually some untyped programming languages based on λ : Lisp or Scheme.
On the contrary, as most recent functional programming languages (e.g OCaml or Haskell), Coq is strongly typed : the non-well-typed terms will be rejected in Coq during a type-checking phase, before running any computation.
For instance, the expressions 1 + true and negb 42 are ill-typed and rejected at type-checking with some error messages:
The term "true" has type "bool" while it is expected to have type "nat".
and
The term "42" has type "nat" while it is expected to have type "bool".

Fail Check 1 + true.

Fail Check negb 42.

The main type constructor is the functional arrow .
Note that A B C is A (B C).
First, simply-typed lambda-calculus : just take the lambda-calculus plus a typing judgment Γ t:T described by the rules below. Here Γ is a typing context, i.e. a finite list of variables associated with their respective types.
  • If x:T is in Γ then Γ x:T
  • If Γ+x t then Γ (λx.t):(τ→σ)
  • If Γ t:(τ→σ) and Γ u then Γ (t u) : σ
These rules are often presented in a tree-shape way, in the form of inference rules:
__________(Var) if x:T in Γ
 Γ ⊢ x:T

  Γ+x:τ ⊢ t:σ
__________________(Lam)
 Γ ⊢ (λx.t):(τ→σ)
and
Γ ⊢ t:(τ→σ)        Γ ⊢ u:τ
____________________________(App)
     Γ ⊢ (t u) : σ
Note that by forgetting all the terms in the previous rules and keeping only the types (and contexts being hence just list of types), we recover the logical rules of the minimal logic : the axiom rule and the implication intro/elim rules.
That is the starting point of the Curry-Howard correspondence (more on that in other courses).
Coq typing system (named CIC, for "Calculus of Inductive Construction") is an (huge) extension of these basic rules. For instance, we will be able to define extra (inductive) types or (recursive) functions.


Computations

Computation rule for λ-calculus : the β-reduction, where (λx.t) u gives t{x:=u} (for some reasonable definition of substitution).
In λ-calculus, this reduction may occur anywhere, in any order. Example of theoretical property of this reduction : the confluence (see for instance https://plfa.github.io/Confluence/ ).
Famous example of term whose reduction is infinite : Δ Δ 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).
Now, in simply typed λ-calculus, computations interact nicely with types :
  • 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 form t₀ of t.
Coq has the same β-reduction rule. Unlike in OCaml, this reduction may occur anywhere (but a concrete command such as Compute does implement a particular strategy).
Coq satisfies a subject reduction property, which is critical to exclude a whole family of runtime errors just by static typing (e.g. no segfaults in OCaml). Roughly, if it types, it runs ok.
Of course, no direct equivalent of Δ Δ 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

No syntactic differences between Coq types and other terms : in Coq everything is term (unlike OCaml for instance).
Morally, a type is just something that may occur on the right of a typing judgment Γ t.
Now, since a type is in particular a Coq term, it should have a type. In Coq a important property is that whenever Γ t and Γ τ:s, then s is necessarily a sort (or universe), i.e. one of the constants Type or Set or Prop. This gives us a concrete definition of a Coq type : anything whose type is a sort.
  • 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
To avoid paradoxes coming from Γ Type : Type, actually Type is not just one universe but a hierarchy of universes:
Type₀ : Type₁ : Type₂ : ...
Normally these indices are hidden to the users, and we will not say more about that here.
In Coq, the arrow type AB is actually not a primitive construction, it is a particular case of a product x:A,B (Coq syntax x:A, B). When variable x does not occur in B (non-dependent product), we write AB. For a first example of dependent product, see the definition of identity below.
Roughly, the typing rule for a product looks like :
  • If Γ A:Type and Γ+x:A B:Type then Γ ( x:A,B) : Type
In reality, one must take care of the indices of the Type universes : the rightmost index is the max of the other two indices:
  • If Γ A: Type(i) and Γ+x:A B:Type(j) then Γ ( x:A,B) : Type(max(i,j))
Or, written as an inference rule:
Γ ⊢ A:Type(i)         Γ+x:A ⊢ B:Type(j)
________________________________________(Prod-Type)
  Γ ⊢ (forall x:A,B) : Type(max(i,j))
(Note that this typing rule has a particular case for Prop that we will not detail here but will come back to it in a future part of the course.)
We can now generalize the typing rules for functions and applications:
  • 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}
Or, written as inference rules:
       Γ+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}
Note that for a non-dependent product, we recover the former rules for AB. In particular for (App), if x does not occur in B then B{x:=u} = B.


Coq constants, global definitions and declarations.

Alongside variables and their types, a Coq typing context (or environment) Γ may also contain constants (c:t:=u) where c is the constant name, t and u are Coq terms giving the type and the definition body for this constant.
Of course, the system checks that Γ u:t before accepting to add this constant c to the environment.
A reduction rule named δ (delta) allows to replace c by its body u at any time.
Beware of the vocabulary: since u may be a fun, this Coq constant may actually be a function, hence not so constant after all...
Coq syntax for adding such a global constant to the system:

Definition c : t := u.
When Coq is able to infer the type t from definition body u, writing : t is not mandatory. This is quite frequent (but not all Coq terms have a type that can be inferred automatically).
For a function definition (i.e. a body starting with fun), we could also write the arguments just after the function name.
For instance, all the following lines are equivalent:

Definition id_1 : X, XX := fun (X:Type)(x:X) ⇒ x.

Definition id_2 : X, XX := fun X xx.
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 (XX) (id X) in
  let id4 X := id2 (XX) (id2 X) in
  id4.

A let x := t in u behaves almost like ((fun xu) 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.

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.

A section is a sort of block mechanism for Coq:

Section mysection.
...
End mysection.
They can in particular be used to declare local variables, with the syntax Variable x: T or Variables x y : T:

Section Identity.

Variable X: Set.

Definition identity := fun x: Xx.

Check identity.
Print identity.

End Identity.

Check identity.
Print identity.

It is also possible, in a section, to write local definitions with the syntax Let v:T := t or Let v := t. When the section is closed, the local definition is turned into a local binding:

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

Define a function compose : A B C, (BC)->(AB)->(AC), using the section mecanism. Test it with functions S and pred on natural numbers (type nat).


Exercise 2 : Boolean ersatz

Define (without using bool nor any other inductive type):
  • 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

Encode in Coq the Church numerals, for which the number n is represented by λf.λx.(f (f (... (f x)))) where f is applied n times.
More precisely, define (without using nat nor any other inductive type):
  • a type church : Type
  • two constant zero and one of type church
  • a function succ of type churchchurch
  • two functions plus and mult of type churchchurchchurch
  • a function power
  • a test iszero
Also define two functions nat2church : nat church and church2nat : church nat


First data types : Boolean and natural numbers.

Coq provides a type bool and constants true and false. There is a construction if ... then ... else ..., as well as some predefined boolean operations :
  • 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)
Beware, unlike usual programming languages, the evaluation of && and || is not necessarily lazy in Coq. We can actually specify the type of evaluation strategy in variants of Compute to be studied later in the semester.


Exercise 4 : Booleans

  • Write a function checktauto : (boolbool)->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.


Coq provides a type nat for natural numbers. By default, typing 0, 1 and any other positive numeric constant gives you a nat. Beware : this is a "unary" encoding (Peano numerals), hence dramatically slow. Those Peano's numbers are built from a constant O (the upper-case letter "o") and a function symbol S for the successor: the use of the usual notation for natural number is a pretty-printing feature.
In order to get access to operations on natural numbers, we load the Arith library (for more informations on the standard library, visit the corresponding section of the reference manual):

Require Import Arith.

Some operations defined on nat:
  • addition +,
  • multiplication ×,
  • euclidean division /,
  • modulo x mod y.
  • boolean comparisons of numbers : x =? y or x <? y or x <=? y.
We will see later how to perform efficient arithmetical operations (binary encoding) and how to handle negative numbers (type Z).


Recursivity

Fixpoint allows to reuse the function we are actually defining in itself !
Before accepting a Fixpoint, Coq checks that this definition is syntactically decreasing. For that, a criterion of structural decrease is used, we will detail it later. In short, recursive calls must be done on strict sub-terms. Practically, this means using a construction match ... with ... end for accessing the previous integer (for instance).

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.

Define the following functions of type nat nat nat (without using the ones of Coq standard library of course!):
  • addition
  • multiplication
  • subtraction
  • power
  • gcd
We recall that the Ackermann-Péter function, AP, is defined as:
  • AP(0,n) = n+1
  • AP(m+1, 0) = AP(m, 1)
  • AP(m+1, n+1) = AP(m, AP(m+1, n)).
Try defining AP, of type nat nat nat, in the most natural way, based on its definition. What problem do you encounter?
What possible workaround can you imagine?


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

We aim here at programming the Zeckendorf theorem in practice : every number can be decomposed in a sum of Fibonacci numbers, and moreover this decomposition is unique as soon as these Fibonacci numbers are distinct and non-successive and with index at least 2.
Load the list library:

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

Functions may be given as arguments to other functions, or come as answers.
Functions may receive less that the expected number of arguments.

Check Init.Nat.add.

Definition plus3 := Init.Nat.add 3.

Check plus3.

Definition apply := fun A B (x:A) (f: AB) ⇒ f x.

Check apply.

Definition k := (fun (g : natnatnat) h (x y z : nat) ⇒ g x (S (h y z))).

Check k.

General recursivity and logical consistency

Coq is logically sound as long as we cannot produce a closed proof of False, a type which is normally empty, since it is an inductive type with no constructor:

Print False.

Here closed means without variables nor axioms in the typing environment. Without even knowing how False is defined in 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.
Obviously such a definition is rejected by 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

Similarly, 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.
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.

Fail Fixpoint flipflop (b:bool) := negb (flipflop b).


Inductive types

The keyword Inductive allow to enrich the system with a new type definition, expressed via several constructor rules. The general syntax of a inductive type declaration is :

Inductive t :=
| C₁ : A₁₁ → ... → A₁ₚ → t
| ...
| Cₙ : A → ... → Aₙₖ → t
The C are constructors of type t, they may require some arguments (or not), but anyway they always have t as result type (after the rightmost arrow).
In fact, t itself may have arguments, turning it into an inductive type scheme (we also say inductive predicate). We'll see examples of that later.
Basic examples (already in the Coq standard library, no need to copy-paste them).

Inductive unit : Set := tt : unit.

Inductive bool :=
| true : bool
| false : bool.

Inductive nat :=
| O : nat
| S : natnat.

Print unit.
Print bool.
Print nat.

Positivity constraints

Some inductive declarations are rejected by Coq, once again for preserving logical soundness. Roughly speaking, the inductive type being currently declared cannot appear as argument of an arguments of a constructor of this type. This condition is named strict positivity.
Illustration of the danger, in OCaml:


type lam = Fun : (lamlam) → lam
let identity = Fun (fun tt)
let app (Fun f) g = f g
let delta = Fun (fun xapp 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 xapp x x)
let dd : 'a = app delta delta
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).


Match

The match operator (or pattern-matching) is a case analysis, following the different possible constructors of an inductive type. It is very similar to OCaml's match, except for little syntactic differences ( in "branches", final keyword end).

match ... with
| C₁ x₁₁ ... x₁ₚ ⇒ ...
| ...
| Cx ... xₙₖ ⇒ ...
end
The head of a match (what is between match and with) should be of the right inductive type, the one corresponding to constructors C₁ ... C.
Usually, the branches (parts after ) contains codes that have all the same type. We'll see later that this is not mandatory (see session on dependent types ).
Computation and match : when the head of a match starts with a inductive constructor Ci, a iota-reduction is possible. It replaces the whole match with just the branch corresponding to constructor Ci, and also replaces all variables xi₁...xi by concrete arguments found in match head after Ci.
Example:

Compute
 match S (S O) with
 | OO
 | S xx
 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

The Fixpoint construction allows to create recursive functions in Coq. Beware, as mentionned earlier, some recursive functions are rejected by Coq, which only accepts structurally decreasing recursive functions.
The keyword Fixpoint is to be used in replacement of Definition, see examples below and exercise to follow.
Actually, there is a more primitive notion called fix, allowing to define an internal recursive function, at any place of a code. And Fixpoint is just a Definition followed by a fix. More on that later, but anyway, favor Fixpoint over fix when possible, it's way more convenient.
A Fixpoint or fix defines necessarily a function, with at least one (inductive) argument which is distinguished for a special role : the decreasing argument or guard. Before accepting this function, Coq checks that each recursive call is made on a syntactic strict subterm of this special argument. Roughly this means any subpart of it is obtained via a match on this argument (and no re-construction afterwards). Nowadays, Coq determines automatically which argument may serve as guard, but you can still specify it manually (syntax {struct n}).
Computation of a Fixpoint or fix : when the guard argument of a fixpoint starts with an inductive constructor Ci, a reduction may occur (it is also called iota-réduction, just as for match). This reduction replaces the whole fixpoint with its body (what is after the :=), while changing as well in the body the name of the recursive function by the whole fixpoint (for preparing forthcoming iterations).

Some usual inductive types


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

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.

Remember that unit is the inductive type with one constructor and a single element.

Fixpoint pow n : Type :=
 match n with
 | 0 ⇒ unit
 | S n ⇒ (nat × (pow n))%type
 end.

The option type over a type A is a datatype providing the option to return an element of A, or none. It is useful to treat cases where there are exceptional situations, typically some object being undefined. the option type is defined in Coq as an inductive type with two constructors, Some and None:

Print option.


Print list.

Require Import List.
Import ListNotations.

Check (3 :: 4 :: []).

Fixpoint length {A} (l : list A) :=
 match l with
 | [] ⇒ 0
 | x :: lS (length l)
 end.

No predefined type of trees in Coq (unlike list, option, etc). Indeed, there are zillions of possible variants, depending on your precise need. Hence each user will have to define its own (which is not so difficult). For instance here is a version with nothing at leaves and a natural number on nodes.

Inductive tree :=
| leaf
| node : nattreetreetree.

Exercise 9: Binary trees with distinct internal and external nodes.

By taking inspiration from the definition of lists above, define an inductive type iotree depending on two types I and O such that every internal node is labelled with an element of type I and every leaf is labelled with an element of type O.


Exercise 10: Lists alternating elements of two types.

By taking inspiration from the definition of lists above, define an inductive type ablists depending on two types A and B which is constituted of lists of elements of types alternating between A and B.


Practice: TP 1

Exercise 1 : Function composition

Define a function compose : A B C, (BC)->(AB)->(AC). Test it with functions S and pred on natural numbers (type nat).


Exercise 2 : Boolean ersatz

Define (without using bool nor any other inductive type):
  • 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

Encode in Coq the Church numerals, for which the number n is represented by λf.λx.(f (f (... (f x)))) where f is applied n times.
More precisely, define (without using nat nor any other inductive type):
  • a type church : Type
  • two constant zero and one of type church
  • a function church_succ of type churchchurch
  • two functions church_plus and church_mult of type churchchurchchurch
  • a function church_power
  • a test church_iszero
Also define two functions nat2church : nat church and church2nat : church nat


Exercise 4 : Booleans

  • Write a function checktauto : (boolbool)->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.

Define the following functions of type nat nat nat (without using the ones of Coq standard library of course!):
  • addition
  • multiplication
  • subtraction
  • power
  • gcd
We recall that the Ackermann-Péter function, AP, is defined as:
  • AP(0,n) = n+1
  • AP(m+1, 0) = AP(m, 1)
  • AP(m+1, n+1) = AP(m, AP(m+1, n)).
Try defining AP, of type nat nat nat, in the most natural way, based on its definition. What problem do you encounter?
What possible workaround can you imagine?


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

We aim here at programming the Zeckendorf theorem in practice : every number can be decomposed in a sum of Fibonacci numbers, and moreover this decomposition is unique as soon as these Fibonacci numbers are distinct and non-successive and with index at least 2.
Load the list library:

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.


Exercise 9: Binary trees with distinct internal and external nodes.

By taking inspiration from the definition of lists above, define an inductive type iotree depending on two types I and O such that every internal node is labelled with an element of type I and every leaf is labelled with an element of type O.


Exercise 10: Lists alternating elements of two types.

By taking inspiration from the definition of lists above, define an inductive type ablists depending on two types A and B which is constituted of lists of elements of types alternating between A and B.


This page has been generated by coqdoc