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 A→B 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 A→B. 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 A→B. In particular for (App), if x does not occur in B then B{x:=u} = B. Coq constants, global definitions and declarations. Global definitions. 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, 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. Global declarations. 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. Section mechanism and local variables 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: X ⇒ x. Check identity. Print identity. End Identity. Check identity. Print identity. Local definition 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, (B→C)->(A→B)->(A→C), 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 church→church two functions plus and mult of type church→church→church a function power a test iszero Also define two functions nat2church : nat → church and church2nat : church → nat First data types : Boolean and natural numbers. bool 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 : (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 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? 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 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: 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 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 : nat → nat. 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 : (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 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₁ₚ ⇒ ... | ... | Cₙ xₙ₁ ... 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 | 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 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 nat : natural numbers represented as Peano integers Print nat. Binary representation of numbers 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 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 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. The list type 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 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 : nat → tree → tree → tree. 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 Functions and only that Exercise 1 : Function composition Define a function compose : ∀ A B C, (B→C)->(A→B)->(A→C). 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 church→church two functions church_plus and church_mult of type church→church→church a function church_power a test church_iszero Also define two functions nat2church : nat → church and church2nat : church → nat 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. 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? 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 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. Some inductive types 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