Programmation fonctionnelle

publicité
USTL - Licence info 3ème année
2008-2009
Expression Logique et Fonctionnelle . . . Évidemment
Programmation fonctionnelle
octobre 2008
2
Chapitre 1
Programmation fonctionnelle
1.1
Programmation fonctionnelle
Lisp
Premier langage informatique fonctionnel :
(n des années 1950).
Base théorique : λ-calcul inventé par Alonzo Church dans les années 1930 pour dénir la notion de
calculabilité.
Nombreux langages fonctionnels :
,
,
,
,
, . . . qui se distinguent
selon diérents critères
Sans ou avec eets de bord : un langage fonctionnel sans eet de bord, ou langage pur, est un
langage où l'aectation n'existe pas. Les langages purs restent assez rares, citons
parmi
eux.
,
, la famille des langages
sont impurs.
Typés dynamiquement ou statiquement : les arguments d'une fonction doivent être d'un certain
type pour que l'évaluation d'une application puisse avoir lieu. La vérication du type, ou typage,
peut se faire à l'exécution du programme, on parle alors de typage dynamique. C'est le cas de
langages comme
ou
. Inversement, le typage peut avoir lieu avant l'exécution du
programme, lors de la compilation, le typage est alors qualié de statique. C'est le cas des langages
de la famille
.
Distinction entre programmation impérative et programmation fonctionnelle :
Notions de base en programmation impérative : état d'une machine, instructions, temps (séquence),
itération. Nécessité d'avoir une connaissance minime du modèle de machine sur laquelle on programme (modèle de Von Neumann).
Notions de base = valeurs, expressions et fonctions. L'exécution d'un programme est l'évaluation
d'une ou plusieurs expressions, expressions qui sont souvent des applications de fonctions à des
valeurs passées en paramètre1 . Pas besoin de modèle de machine.
Lisp Scheme Haskell SML OCaml
Lisp Scheme
Lisp
ML
Haskell
Scheme
ML
1.1.1
Caml
OCaml
ML
Langage utilisé :
, dialecte de la famille
. C'est un langage développé à l'INRIA2 (http ://caml.inria.fr).
Ce langage ore
un noyau fonctionnel ;
des structures de contrôle impératives, ainsi que des types de données mutables ;
la possibilité de programmation par objets.
Il est utilisé
dans l'enseignement (classes prépa, universités) ;
dans l'industrie (CEA, France Telecom, EDF) ;
et il est particulièrement adapté
au prototypage d'applications ;
aux preuves de programmes (cf Coq) ;
compilation et interprétation.
1 On dit aussi programmation applicative
2 Institut National de Recherche en Informatique
et Automatique
3
4
CHAPITRE 1.
1.2
PROGRAMMATION FONCTIONNELLE
Premiers pas
1.2.1 Phrases du langage
OCaml
Un programme fonctionnel écrit en
est une suite de phrases, déclarations de variables ou de
types, ou bien expressions à évaluer. L'exécution d'un programme consiste en l'évaluation de toutes les
phrases.
Les phrases du langage sont terminées par un double point-virgule ( ; ;). Ci-dessous quelques exemples
de phrases évaluées par l'interprète du langage (Le symbole # est le prompt de la boucle d'interaction, ce
qui suit est la phrase à évaluer. Les lignes débutant par le symbole - sont les résultats de l'évaluation de
la phrase).
Objective Caml version 3.09.2
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
-
132 ;;
: int = 132
2*(45+10) ;;
: int = 110
7/4 ;;
: int = 1
7 mod 4 ;;
: int = 3
2. ;;
: float = 2.
2. +. 3. ;;
: float = 5.
7./.4. ;;
: float = 1.75
float_of_int 2 ;;
: float = 2.
true ;;
: bool = true
true & false ;;
: bool = false
1 = 2-1 ;;
: bool = true
"Hello" ;;
: string = "Hello"
"Hello " ^ "le monde " ;;
: string = "Hello le monde "
’A’ ;;
: char = ’A’
int_of_char ’B’ ;;
: int = 66
1.2.2 Types de base
OCaml
ML
, comme tous les langages de la famille
, est un langage fortement typé, à typage statique.
Avant d'évaluer une phrase, le type de l'expression est calculé, c'est l'inférence de type : contrairement
à des langages comme ,
,
, le programmeur n'a pas besoin de préciser le type de ces
expressions, variables ou fonctions. Le type d'une expression est calculé à partir de ses composants. Si
l'inférence de type échoue, l'expression n'est pas évaluée. Si en revanche elle réussit, l'expression peut être
évaluée.
Voici les types de base du langage :
C Pascal Ada
int :
les entiers. L'intervalle du type int dépend de l'architecture de la machine utilisée. C'est l'intervalle
[[−230 , 230 − 1]] sur les machines 32 bits.
1.2.
5
PREMIERS PAS
Opérateur
+
*
/
mod
signication
addition
soustraction
multiplication
division entière
reste de la division entière
oat :
OCaml
les nombres ottants. Ils respectent la norme IEEE 754.
distingue les entiers des ottants :
2 est de type int, tandis que 2. est de type float, et ces deux valeurs ne sont pas égales. Même les
opérateurs arithmétiques sur les ottants sont diérents de ceux sur les entiers.
Opérateur signication
+.
addition
-.
soustraction
multiplication
*.
/.
division
exponentiation
**
On ne peut pas additionner un int et un float
# 2 + 3. ;;
Characters 4-6:
2 + 3. ;;
^^
This expression has type float but is here used with type int
# 2. +. 3 ;;
Characters 6-7:
2. +. 3 ;;
^
This expression has type int but is here used with type float
Fonction
float_of_int
ceil
floor
sqrt
exp
log
log10
...
string :
signication
conversion d'un int en float
partie entière supérieure (plafond)
partie entière inférieure (plancher)
racine carrée
exponentielle
logarithme népérien
logarithme décimal
...
les chaînes de caractères (longueur limitée à 264 − 6).
Opérateur
^
signication
concaténation
Fonction
string_of_int
int_of_string
string_of_float
float_of_string
String.length
char :
signication
conversion d'un
conversion d'un
conversion d'un
conversion d'un
longueur
int en un string
string en un int
float en un string
string en un float
les caractères. Au nombre de 256, les 128 premiers suivent le code ASCII.
Fonction
signication
char_of_int conversion d'un int en un char
int_of_char conversion d'un char en un int
Opérateur
bool
signication
négation
: les booléens pouvant prendre deux valeurs true et false.
et séquentiel
ou séquentiel
Les opérateurs de comparaison sont polymorphes, ils sont utilisables pour comparer deux int, ou
deux float, ou deux string,. . . . Mais ils ne permettent pas la comparaison d'un int et un float.
not
&& ou &
|| ou or
6
CHAPITRE 1.
Opérateur
=
<>
<
<=
>
>=
unit :
PROGRAMMATION FONCTIONNELLE
signication
égalité (structurelle)
négation de =
inférieur
inférieur ou égal
supérieur
supérieur ou égal
le type unit est un type ne possédant qu'une seule valeur notée (). Ce type est utilisé dans la
partie impérative du langage
, et par les diérentes procédures d'achage.
Procédure
signication
print_int
achage d'un entier
print_float
achage d'un ottant
print_string
achage d'une chaîne de caractères
print_char
achage d'un caractère
print_newline passage à une nouvelle ligne
OCaml
# print_int 12 ;;
12- : unit = ()
# print_float 3.14 ;;
3.14- : unit = ()
# print_string"Hello" ;;
Hello- : unit = ()
# print_char ’A’ ;;
A- : unit = ()
# print_newline () ;;
- : unit = ()
1.2.3 Déclaration de variables
En programmation fonctionnelle, une variable est une liaison entre un nom et une valeur. Les variables
peuvent être globales, et elles sont alors connues de toutes les expressions qui suivent la déclaration, ou
bien locales à une expression, et dans ce cas elles ne sont connues que de l'expression pour laquelle elles
ont été déclarées.
L'ensemble des variables connues d'une expression est appelé environnement de l'expression.
La syntaxe d'une déclaration globale est de la forme
let <nom> = <expr> ;;
OCaml
où < nom > est le nom de la variable (en
les noms de variables doivent obligatoirement
commencer par une lettre minuscule), et < expr > une expression décrivant la valeur de la variable.
# let n = 12 ;;
val n : int = 12
# n ;;
- : int = 12
# 2 * n;;
- : int = 24
# let m = 2 * n ;;
val m : int = 24
# m + n;;
- : int = 36
# m + p;;
Characters 2-3:
m+p;;
^
Unbound value p
Pour être typable, et donc évaluable, toutes les variables d'une expression doivent être dénies dans
l'environnement de l'expression, sous peine d'avoir le message d'erreur Unbound value.
1.2.
PREMIERS PAS
7
On peut aussi déclarer simultanément plusieurs variables.
let <nom1 > = <expr1 >
and <nom2 > = <expr2 >
...
and <nomn > = <exprn > ;;
1.2.4 Déclaration locale de variables
Syntaxe des déclarations locales
let <nom> = <expr1 >
in <expr2 > ;;
Cette forme syntaxique permet de déclarer la variable nommée < nom > dans l'environnement d'évaluation de l'expression < expr2 >. En dehors de l'environnement de cette expression, la variable < nom >
n'existe pas.
Il est possible de faire des déclarations simultanées devariables locales à une expression.
let nom1 = expr1
and nom2 = expr2
...
and nomn = exprn
in exprn+1 ;;
# let pi = 3.141592
and r = 2.0
in 2. *. pi *. r ;;
- : float = 12.566368
# pi ;;
Characters 0-2:
pi ;;
^^
Unbound value pi
1.2.5 Expression conditionnelle
La syntaxe d'une expression conditionnelle est
if <expr1 > then <expr2 > else <expr3 > ;;
dans laquelle
< expr1 > est une expression booléenne (type bool),
< expr2 > et expr3 ont le même type.
# if 1=1 then "égaux" else "non égaux" ;;
- : string = "égaux"
# if 1=2 then "égaux" else "non égaux" ;;
- : string = "non égaux"
# if 1=2 then 0 else "non égaux" ;;
Characters 19-30:
if 1=2 then 0 else "non égaux" ;;
^^^^^^^^^^^
This expression has type string but is here used with type int
Remarques :
OCaml
OCaml
en
, la structure de contrôle conditionnelle est une expression et non une instruction, c'est
la raison pour laquelle les deux expressions des parties then et else doivent être du même type.
en
, il est possible d'omettre la partie else d'une expression conditionnelle. Dans ce cas,
elle est considérée comme étant la valeur () (type unit), et la partie then doit elle aussi être du
type unit.
8
CHAPITRE 1.
PROGRAMMATION FONCTIONNELLE
1.2.6 Fonctions
OCaml
En
, comme dans tous les langages fonctionnels, les fonctions sont des valeurs comme toutes
les autres. Une variable peut donc avoir une fonction pour valeur.
# let racine_carree = sqrt ;;
val racine_carree : float -> float = <fun>
# racine_carree 2. ;;
- : float = 1.41421356237309515
Type d'une valeur fonctionnelle
Le type d'une fonction est déterminé par le type de son (ou ses) argument(s) et celui des valeurs
qu'elle renvoie. Ainsi le type de la fonction sqrt est float -> float puisqu'elle prend un float pour
paramètre et retourne un float.
Dénition d'une fonction
Le premier mot-clé permettant de dénir une fonction est function
function <param> -> <expr >
où param est le nom donné au paramètre formel, et expr est l'expression à évaluer lors d'un appel à
la fonction.
# function x -> x*x ;;
- : int -> int = <fun>
Dans l'exemple cidessus, l'évaluation de la phrase donne comme résultat une valeur fonctionnelle
de type int -> int. Mais cette fonction n'est pas nommée. Il est bien entendu possible de nommer une
fonction en déclarant une variable (globale ou non)
# let carre = function x -> x*x ;;
val carre : int -> int = <fun>
Il existe une autre façon de déclarer une variable fonctionnelle, tout à fait équivalente à la première,
et qui n'utilise pas le mot-clé function.
let f x = expr ;;
Par exemple, notre fonction carre peut être déclarée sous la forme
# let carre x = x*x ;;
val carre : int -> int = <fun>
Appel à une fonction
Pour appliquer une fonction f à une valeur v , il sut d'écrire le nom de la fonction suivi de la valeur
f v
#
#
-
carre
: int
carre
: int
2 ;;
= 4
(2 + 2) ;;
= 16
Attention aux parenthèses qui permettent de contrôler la priorité des opérateurs
# carre 2+2 ;;
- : int = 6
À noter que la syntaxe plus traditionnelle
f (v )
est acceptable en
Caml parce que l'expression (v) a pour valeur celle de v.
1.3.
9
EXERCICES
#
#
-
(2);;
: int = 2
carre (2);;
: int = 4
1.2.7 Déclarations récursives
Envisageons la dénition de la fonction calculant la factorielle de l'entier passé en paramètre. Une
expression récursive classique de n! est
0!
=
1
n!
=
n × (n − 1)! ∀n ∈ N∗
On peut être tenté de traduire cette formulation par la déclaration
# let fact n =
if n=0 then 1 else n*fact(n-1) ;;
mais on a tort car on obtient le message
Characters 37-41:
if n=0 then 1 else n*fact(n-1) ;;
^^^^
Unbound value fact
qui indique que la variable fact apparaissant dans l'expression la dénissant n'est pas liée dans l'environnement de déclaration de la variable . . . fact. On tourne en rond.
Pour remédier à cela, il faut utiliser la forme syntaxique let rec
let rec <nom> = <expr >
dans laquelle toute référence à la variable < nom > dans l'expression < expr > est une référence à la
variable en cours de déclaration.
# let rec fact n =
if n=0 then 1 else n*fact(n-1) ;;
val fact : int -> int = <fun>
# fact 5 ;;
- : int = 120
1.3
Exercices
Exercice 1. Évaluations d'expressions
Pour la session qui suit, donnez le type et la valeur de chacune des phrases
let n1 = 10 ;;
let n2 = n1+5 ;;
let n1 = n2+6 ;;
let vrai = true ;;
vrai && false ;;
vrai && true ;;
let x = n1*n2 > n2*n2 ;;
let y = x+1 ;;
let z = 4 in z+2*z ;;
let z = let z = 4 in z+2*z ;;
let y = let x = z*z in x+2 ;;
let message = let personnage = "La gardienne "
and lieu = "du campus "
and action = "dessine"
in personnage ^ lieu ^ action ;;
print_string (message^"\n") ;;
10
CHAPITRE 1.
PROGRAMMATION FONCTIONNELLE
Exercice 2. Un calcul de puissance
En remarquant que a7 = a × a2 × (a2 )2 écrivez une expression calculant 137 en 4 multiplications de
nombres entiers. Votre expression ne devra pas modier l'environnement.
Exercice 3. Signe d'un entier
Typez, puis écrivez la fonction qui à un entier positif associe 1, à un entier négatif associe -1, et à
l'entier nul associe 0.
Exercice 4. Ou exclusif
Réalisez la fonction xor qui calcule le ou-exclusif de ces deux arguments.
Exercice 5. Nombres de Fibonacci
On rappelle que les nombres de Fibonacci sont dénis par récurrence par
F0
=
0
F1
=
1
Fn+2
= Fn+1 + Fn ∀n ∈ N
Réalisez la fonction fibo de type int → int telle que la valeur de fibo n soit égale à Fn .
Exercice 6. Chaînes de caractères
OCaml
En
, on peut utiliser la fonction sub, prédénie dans le module String, pour extraire une
souschaîne d'une chaîne de caractères.
String.sub s a b
donne la sous-chaîne de s de longueur b démarrant à la position a (la position du premier caractère est
0).
Par exemple :
# String.sub "Expression Logique et Fonctionnelle Evidemment" 22 13;;
- : string = "Fonctionnelle"
La fonction length du module String donne la longueur d'une chaîne.
# String.length "Expression Logique et Fonctionnelle Evidemment" ;;
- : int = 46
Réalisez le prédicat est_palindrome qui teste si la chaîne passée en paramètre est un palindrome.
Exercice 7. Fonction bien dénie ?
Question 1. Quel est le type de la fonction f ? Quelles sont les valeurs qu'elle peut prendre ?
let rec f x =
if abs_float (x -. 1.) < 0.001 then true
else f ((x +. 1.) /. 2.) ;;
Question 2.
Est-elle partout dénie ? Estimez en fonction de la valeur de x, le nombre d'appels récursifs.
Chapitre 2
Ordre supérieur - Polymorphisme
2.1
Ordre supérieur
L'un des points forts de la programmation fonctionnelle est de considérer les fonctions au même niveau
que toute autre type de données. À ce titre il est possible d'eectuer des traitements sur des fonctions,
et d'écrire des fonctions prenant des fonctions en paramètres et/ou produisant des fonctions en sorties.
De telles fonctions dont qualiées de fonctions d'ordre supérieur.
2.1.1 Les fonctions d'arité supérieure à 1
Toute fonction d'arité supérieure à 1 peut être considérée en
supérieur, à savoir une fonction d'arité 1 à valeur fonctionnelle.
Par exemple la fonction
OCaml comme une fonction d'ordre
let add x y = x+y ;;
est de type int -> int -> int. Cette fonction peut être vue comme une fonction d'arité 2
# add 1 2 ;;
- : int = 3
La dénition précédente peut être remplacée par une autre forme équivalente
let add = function x -> function y -> x+y ;;
forme qui montre que la fonction add peut aussi être vue, et utilisée, comme une fonction d'arité 1
# add 1 ;;
- : int -> int = <fun>
On peut noter que la valeur de l'expression add 1 est du type fonctionnel int -> int, autrement dit le
résultat est une fonction qui peut elle-même être appliquée à un entier.
# (add 1) 2 ;;
- : int = 3
En fait les deux expressions add 1 2 et (add 1) 2 expriment exactement la même chose.
En
, toutes les fonctions sont d'arité 1.
Toute déclaration de la forme
Caml
let f x1 x2 ... xn = expr
peut être déclarée par
let f = function x1 -> function x2 -> ... -> function xn -> expr
Les fonctions à plusieurs paramètres sont en fait des fonctions à un paramètre qui retournent une valeur
d'un type fonctionnel.
Le mot-clef fun permet une écriture plus concise
let f = fun x1 x2 ... xn -> expr
11
12
CHAPITRE 2.
ORDRE SUPÉRIEUR - POLYMORPHISME
Par exemple
let add = fun x y -> x+y ;;
Le type de la fonction add est int -> (int -> int) qui est synonyme de int -> int -> int.
Les types fonctionnels sont parenthésés à droite :
σ1 → σ2 → . . . σn−1 → σn = σ1 → (σ2 → . . . (σn−1 → σn ) . . .)
Attention, le type int -> int -> int est diérent du type (int -> int) -> int, comme on peut le
constater ici
# let carre x = x*x;;
val carre : int -> int = <fun>
# add carre ;;
Characters 4-9:
add carre ;;
^^^^^
This expression has type int -> int but is here used with type int
Les types fonctionnels ne sont pas parenthésés à gauche.
2.1.2 Fonctionnelles
De la même façon que le type de la valeur retournée par une fonction peut être une fonction, le type
d'un paramètre peut aussi être une fonction. Une fonctionnelle est une fonction prenant une fonction en
argument.
Par exemple, la fonction
# let demiValeurEnZero f =
(f 0) / 2 ;;
val demiValeurEnZero : (int -> int) -> int = <fun>
est une fonctionnelle qui prend pour paramètre toute fonction f de type int -> int et qui retourne la
valeur de f (0)
2 .
#
#
-
2.2
demiValeurEnZero carre ;;
: int = 0
demiValeurEnZero (add 2);;
: int = 1
Polymorphisme
2.2.1 Fonctions polymorphes
Une fonction peut être polymorphe.
Par exemple, la fonction
# let valeurEnZero f =
f 0;;
val valeurEnZero : (int -> ’a) -> ’a = <fun>
est polymorphe car le type de la valeur qu'elle retourne est le même que le type du résultat de la fonction
passée en paramètre.
Le caractère polymorphe de cette fonction est marqué par la variable de type ’a.
Une fonction peut aussi avoir des paramètres polymorphes.
# let zero x = 0;;
val zero : ’a -> int = <fun>
2.2.
13
POLYMORPHISME
2.2.2 Typage d'une expression fonctionnelle
Voici comment calculer le type d'une expression fonctionnelle x 7→ expr dans un environnement Env .
1. Désigner par l'inconnue σ le type de x.
2. Désigner par l'inconnue τ le type de expr. L'expression focntionnelle a alors pour type σ → τ .
3. Typer chacune des sous-expressions de expr dans l'environnement Env . Des contraintes sur les
inconnues σ et τ peuvent voir le jour.
4. En tenant compte de toutes les contraintes sur σ et τ apparues dans le typage de expr, trois
situations peuvent apparaître :
(a) σ et τ sont entièrement déterminés. Le type du paramètre et celui du résultat sont connus.
(b) des contraintes contradictoires sont apparues et expr n'est pas typable. L'expression fonctionnelle ne l'est pas non plus.
(c) σ et/ou τ ne sont pas entièrement déterminés. La fonction x 7→ expr peut convenir pour
plusieurs types de paramètres ou peut avoir plusieurs types de résultat.
Illustrons chacune des situations évoquées.
Exemple 1 :
Considérons la fonction
function x -> x+a > 0
et calculons son type dans l'environnement Env =< a : int = 1 > .Env0 où Env0 est l'environnement
initial standard de
.
OCaml
1. soit σ le type (inconnu) de x
2. soit τ le type (inconnu) de x + a > 0
3. dans l'environnment Env ,
a est de type int,
+ est de type int -> int -> int,
donc x est de type int et σ =int,
sous l'hypothèse σ =int, x + a est typable de type int,
0 est de type int,
> est polymorphe à valeurs booléennes
donc x + a > 0 est typable de type bool, et par conséquent τ =bool.
4. la fonction est donc de type int -> bool.
Exemple 2 :
Considérons maintenant la fonction
function x -> x && (x < 0) ;;
dans l'environnement initial de
OCaml Env0 .
1. soit σ le type (inconnu) de x
2. soit τ le type (inconnu) de x&&(x < 0)
3. contraintes obtenues dans l'environnement Env
dans Env0 , && est de type bool -> bool -> bool
donc x doit être de type bool, σ =bool
l'expression (x < 0) n'est alors pas typable puisque 0 est de type int diérent du type inféré
pour x
4. la fonction n'est pas typable.
ce qu'on peut vérier.
# function x -> x && (x < 0) ;;
Characters 24-25:
function x -> x && (x < 0) ;;
^
This expression has type int but is here used with type bool
14
CHAPITRE 2.
Exemple 3 :
ORDRE SUPÉRIEUR - POLYMORPHISME
Considérons enn la fonction
function x -> x ;;
dans un environnement quelconque Env .
1. soit σ le type (inconnu) de x
2. soit τ le type (inconnu) de x
3. on doit évidemment avoir l'équation σ = τ
4. la liste des contraintes obtenues est limitée l'équation σ = τ , donc le type de la fonction n'est pas
complètement déterminé : aucune contrainte sur le type du paramètre, le résultat a le même type
que le paramètre.
#
#
-
# (function x -> x) 1 ;;
: int = 1
(function x -> x) true ;;
: bool = true
(function x -> x) "elfe" ;;
: string = "elfe"
2.2.3 Typage des fonctions récursives
Pour le typage d'une déclaration de la forme
let rec f = function x -> <expr>
dans un environnement Env , il sut de déterminer les contraintes sur les inconnues σ et τ dans l'environnement augmenté
Env 0 =< f : σ → τ = x 7→< expr >> .Env
2.2.4 Fonctions polymorphes
Les fonctions comme celle de l'exemple 3 sont dites polymorphes car elles peuvent être appelées avec
des arguments ayant des types diérents.
Pour désigner le type d'un argument d'une fonction polymorphe,
trouve le type le plus général
et utilise des variables notées ’a, ’b, . . . pour désigner ces types.
OCaml
# let id x = x ;;
val id : ’a -> ’a = <fun>
Il existe de nombreuses fonctions ou opérateurs polymorphes en
comparaison1 par exemple.
#
#
-
2.3
OCaml, comme les opérateurs de
(=) ;;
: ’a -> ’a -> bool = <fun>
(<) ;;
: ’a -> ’a -> bool = <fun>
Exercices
Exercice 8. ou exclusif
Question 1. Réalisez
la fonction xor qui calcule le ou-exclusif de ses deux arguments. Déterminez le
type de cette fonction.
Question 2. Quelles sont les natures des variables dénies par
let f = xor true;;
let g = xor false;;
1 Ces
opérateurs ne s'appliquent toutefois pas sur des fonctions. L'égalité de deux fonctions est une propriété indécidable.
2.3.
15
EXERCICES
Exercice 9. Sommes
P
Question 1. Réalisez une fonction somme1 paramétrée par les entiers a et b qui calcule bk=a k.
Question 2. Quel est le type de l'expression somme1 a si a est un nombre entier ?
Question 3. Réalisez une
P fonction somme paramétrées par une fonction f de type int -> int et deux
b
entiers a et b qui calcule k=a f (k). Quel est le type de cette fonction ?
Question 4. Comment utiliser la fonction somme pour dénir la fonction somme2 qui calcule la somme
des carrés des entiers de a à b, a et b étant passés en paramètres ?
Exercice 10. Expressions fonctionnelles
Donner des expressions fonctionnelles ayant les types suivants :
1. int -> int -> int
2. (int -> int) -> int
3. int -> (int -> int)
4. int -> int -> int -> int
5. int -> (int -> int) -> int
6. (int -> int) -> int -> int
7. (int -> int -> int) -> int
Exercice 11. Expressions fonctionnelles polymorphes
Donner des expressions fonctionnelles ayant les types suivants :
1. ’a -> ’a
2. ’a -> int
3. ’a -> ’b
4. ’a -> ’a -> bool
5. (’a -> ’b) -> ’a -> ’b
6. (’a -> ’b) -> (’b -> ’c) -> ’a -> ’c
7. (’a -> ’b) -> (’c -> ’b) -> ’a -> ’c -> bool
8. (’a -> int -> int) -> ’a -> ’a -> int
9. (’a -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c
Exercice 12.
Exercice 13. Typage d'expressions fonctionnelles polymorphes
Expliquez pourquoi le type des expressions fonctionnelles qui suivent ne dépend pas de l'environnement
dans lequel elles sont évaluées, et déterminez ce type.
1. fun f x y -> f x y
2. fun f x y -> (f x) y
3. fun f x y -> f (x y)
4. fun f x y -> f (x y f)
5. fun f x y -> (f x) (y f)
6. fun f x y -> (f x) / (f y)
7. fun f x y -> x f (f y)
8. fun f x y -> f (f x y)
Exercice 14. Fonctionnelles polymorphes
Réalisez des fonctions d'ordre supérieur polymorphes pour
1. l'application d'une fonction f à un argument x
2. la composition de deux fonctions f et g
3. l'itération n fois d'une fonction f sur un argument x, ie calcul de f n (x) = f (f (. . . f (x) . . .))
16
CHAPITRE 2.
ORDRE SUPÉRIEUR - POLYMORPHISME
Chapitre 3
Couples,
3.1
Couples,
n-uplets
et listes
n-uplets
3.1.1 Construction de n-uplets :
Les n-uplets sont obtenus avec la virgule :
# 1, "a", true ;;
- : int * string * bool = (1, "a", true)
Le type des expressions ainsi construites est composé des types des diérentes composantes du n-uplet.
Comme on le voit sur l'exemple, il est possible de construire des n-uplets dont les composantes sont de
types diérents.
Un type composé n-uplet correspond à un produit cartésien d'ensembles.
Remarque :
Toutefois, si le produit cartésien d'ensembles est associatif, il n'en est pas de même de la
construction des n-uplets : les types σ1 *σ2 *σ3 , (σ1 *σ2 )*σ3 et σ1 *(σ2 *σ3 ) ne sont pas égaux.
# let a1 = 1,"a",true ;;
val a1 : int * string * bool = (1, "a", true)
# let a2 = (1,"a"),true ;;
val a2 : (int * string) * bool = ((1, "a"), true)
# let a3 = 1,("a",true) ;;
val a3 : int * (string * bool) = (1, ("a", true))
# a1=a2;;
Characters 3-5:
a1=a2;;
^^
This expression has type (int * string) * bool but is here used with type
int * string * bool
# a2=a3;;
Characters 3-5:
a2=a3;;
^^
This expression has type int * (string * bool) but is here used with type
(int * string) * bool
Le type de a1 est un triplet tandis que les types de a2 et a3 sont des couples de types diérents.
3.1.2 Accès aux composantes d'un n-uplet :
On peut accéder aux composantes d'un n-uplet à l'aide d'une déclaration
# let a11,a12,a13 = a1;;
val a11 : int = 1
val a12 : string = "a"
val a13 : bool = true
17
18
CHAPITRE 3.
COUPLES,
N -UPLETS
ET LISTES
Cette déclaration associe à trois variables les composantes du triplet a1.
Pour n = 2, il existe deux focntions prédénies donnant accès aux composantes du couple :
1. fst : ’a * ’b -> ’a donne la première (rst) composante,
2. snd : ’a * ’b -> ’b donne la première (second) composante.
Ces deux fonctions ne peuvent pas s'appliquer sur des n-uplets à plus de deux composantes.
# fst a2 ;;
- : int * string = (1, "a")
# snd a2 ;;
- : bool = true
# fst a1 ;;
Characters 4-6:
fst a1 ;;
^^
This expression has type int * string * bool but is here used with type
’a * ’b
3.1.3 Retour sur l'arité des fonctions
En mathématiques, une fonction dénie sur un ensemble A × B peut être indiéremment considérée
comme une fonction à deux variables x ∈ A et y ∈ B (arité 2), ou bien comme une fonction à une variable
c ∈ A × B (arité 1).
En informatique, les deux points de vue sont diérents.
# let add x y = x+y ;;
val add : int -> int -> int = <fun>
# let plus (x,y) = x+y ;;
val plus : int * int -> int = <fun>
# add 1 2 ;;
- : int = 3
# plus (1,2) ;;
- : int = 3
# add (1,2) ;;
Characters 4-9:
add (1,2) ;;
^^^^^
This expression has type int * int but is here used with type int
# plus 1 2 ;;
Characters 0-4:
plus 1 2 ;;
^^^^
This function is applied to too many arguments,
maybe you forgot a ‘;’
Les deux fonctions add et plus sont diérentes bien que mathématiquement semblables. On dit que
curryée 1 de plus, et inversement, plus est la forme décurryée de add.
Entre forme curryée ou décurryée d'une même fonction, laquelle le programmeur doit-il choisir ?
Cela dépend de l'usage qui en est fait. Cependant il est toujours possible de transformer une fonction
d'une forme à l'autre.
Un argument en faveur de la forme curryée : la possibilité d'appliquer partiellement la fonction.
add est la forme
3.2
Listes
3.2.1 Construction de listes
OCaml
permet de manipuler des listes homogènes, c'est-à-dire constituée d'éléments du même type.
Pour construire des listes on utilise deux constructeurs : [] pour la liste vide, et : : pour l'adjonction
d'un élément en tête de liste.
1 Rien
à voir avec un quelconque mélange d'épices. Du nom du logicien Haskell Curry (1900-1982)
3.2.
19
LISTES
# 1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]
Il est aussi possible de construire une liste par énumération de ses éléments sous la forme
# [1; 2; 3] ;;
- : int list = [1; 2; 3]
Remarque :
La construction d'une liste par énumération de ses éléments utilise le séparateur ; à ne
pas confondre avec le séparateur ,.
# [1,2,3] ;;
- : (int * int * int) list = [(1, 2, 3)]
3.2.2 Le type list
À la diérence des n-uplets, les listes de
OCaml sont homogènes.
# [1; "a"; true] ;;
Characters 4-7:
[1; "a"; true] ;;
^^^
This expression has type string but is here used with type int
OCaml précise le type d'une liste en indiquant σ
#
#
#
-
list où σ est le type des éléments de la liste.
[1 ; 2; 3] ;;
: int list = [1; 2; 3]
[true; false] ;;
: bool list = [true; false]
[(1,2) ; (3,4)];;
: (int * int) list = [(1, 2); (3, 4)]
La liste vide peut être d'un type polymorphe si elle est construite avec le constructeur []
# [] ;;
- : ’a list = []
ou d'un type instancié si elle est obtenue par à partir d'une autre liste
#
#
#
-
List.tl [true] ;;
: bool list = []
List.tl [1] ;;
: int list = []
List.tl ["a"] ;;
: string list = []
3.2.3 Fonctions du module List
Les principales opérations sur les listes sont regroupées dans le module List. Pour les utiliser, il faut
préxer leur nom par le nom du module List (à moins de faire appel préalablement à la commande open
List ; ;.
Voici une liste non exhaustive des fonctions du module List.
hd : ’a list -> ’a donne le premier élément de la liste. CU : la liste ne doit pas être vide.
# List.hd [1;2;3;4] ;;
- : int = 1
# List.hd [] ;;
Exception: Failure "hd".
tl : ’a list -> ’a list donne la liste sans son premier élément.
vide.
CU : la liste ne doit pas être
20
CHAPITRE 3.
COUPLES,
N -UPLETS
ET LISTES
# List.tl [1;2;3;4] ;;
- : int list = [2; 3; 4]
# List.tl [] ;;
Exception: Failure "tl".
length : ’a list -> int donne la longueur de la liste.
#
#
-
List.length [1;2;3;4] ;;
: int = 4
List.length [] ;;
: int = 0
nth : ’a list -> int -> ’a donne l'élément d'indice n de la liste, le premier élément étant
d'indice 0. CU : n doit être inférieur à la longueur de la liste.
# List.nth [1;2;3;4] 0 ;;
- : int = 1
# List.nth [1;2;3;4] 3 ;;
- : int = 4
# List.nth [1;2;3;4] 4 ;;
Exception: Failure "nth".
append : ’a list -> ’a list -> ’a list donne la liste concaténée des deux listes. L'opérateur
@ peut aussi être utilisé à la place de cette fonction.
#
#
-
List.append [1;2;3;4] [5;6] ;;
: int list = [1; 2; 3; 4; 5; 6]
[1;2;3;4] @ [5;6] ;;
: int list = [1; 2; 3; 4; 5; 6]
map : (’a -> ’b) -> ’a list -> ’b list applique une fonction à tous les éléments d'une liste
pour donner la liste des résultats.
# List.map (function x -> x*x) [1;2;3;4] ;;
- : int list = [1; 4; 9; 16]
3.3
Types somme
3.3.1 Déclaration de types
Parmi les phrases du langage
le mot-clé type.
OCaml, certaines sont des déclarations de type. Elles commencent par
Syntaxe :
type <nom> = <def inition> ;;
Déclarations simultanées de types
type <nom1 > = <def inition1 >
and <nom2 > = <def inition2 >
...
and <nomn > = <def initionn >
3.3.2 Constucteurs constants
On peut dénir des types en utilisant des constructeurs constants qui n'attendent pas d'argument.
Les constructeurs sont des noms débutant par une lettre majuscule.
# type couleur = Trefle | Pique | Coeur | Carreau ;;
type couleur = Trefle | Pique | Coeur | Carreau
# Trefle ;;
3.4.
21
FILTRAGE DE MOTIF
#
#
-
: couleur = Trefle
Pique, Carreau ;;
: couleur * couleur = (Pique, Carreau)
[Trefle; Coeur; Carreau] ;;
: couleur list = [Trefle; Coeur; Carreau]
Ada Pascal
Les types ainsi dénis sont les analogues de types énumérés de langages comme
,
, . . . Ils
n'ont qu'un nombre ni de valeurs correspondant à chacun des constructeurs utilisés dans l'énumération.
3.3.3 Constructeurs non constants
L'usage de constructeurs permet de dénir de nouveaux types que l'on peut considérer comme réunion
d'autres types. Le type couleur déni ci-dessus est la réunion de quatre types ayant chacun une valeur.
Avec des constructeurs non constants, on peut réunir des types existant pour n'en faire qu'un seul.
# type entier_ou_booleen = Entier of int | Booleen of bool ;;
type entier_ou_booleen = Entier of int | Booleen of bool
# Entier 1 ;;
- : entier_ou_booleen = Entier 1
# Booleen true ;;
- : entier_ou_booleen = Booleen true
Le type entier_ou_booleen ainsi déclaré réunit en un seul type le type bool et le type int. Les constructeurs Entier et Booleen prennent chacun un argument qui est un type placé après le mot-clé of.
3.3.4 Types récursifs
On peut faire des déclarations récursives de types, par exemple pour dénir des types listes, arbres. . . L'uage du seul mot-clé type sut à la déclaration de types récursifs.
Voici par exemple la déclaration d'un type liste d'entiers
# type liste_d_entiers =
Nil
| Cons of int*liste_d_entiers ;;
type liste_d_entiers = Nil | Cons of int * liste_d_entiers
# Nil ;;
- : liste_d_entiers = Vide
# Cons (1, Nil) ;;
- : liste_d_entiers = Cons (1, Nil)
# Cons(2,Cons (1, Vide)) ;;
- : liste_d_entiers = Cons (2, Cons (1, Nil))
et celui d'un type arbre binaire dont les noeuds contiennent des entiers
# type ’a arbre_binaire =
Vide
| Noeud of ’a * ’a arbre_binaire * ’a arbre_binaire ;;
type ’a arbre_binaire =
Vide
| Noeud of ’a * ’a arbre_binaire * ’a arbre_binaire
# Vide ;;
- : ’a arbre_binaire = Vide
# Noeud (3, Vide, Vide) ;;
- : int arbre_binaire = Noeud (3, Vide, Vide)
# Noeud ("b", Vide, Noeud ("a", Vide, Vide)) ;;
- : string arbre_binaire = Noeud ("3", Vide, Noeud ("1", Vide, Vide))
3.4
Filtrage de motif
Le ltrage de motif (pattern-matching en anglais) est une remarquable et puissante caractéristique
des langages de la famille ML.
22
CHAPITRE 3.
3.4.1 L'expression match
Syntaxe de
COUPLES,
N -UPLETS
ET LISTES
... with
match
match expr with
| m1 -> expr1
| m2 -> expr2
...
| mn -> exprn
Dans une expression match l'expression expr est ltrée séquentiellement par les motifs mi . La valeur
de cette expression est celle de l'expression expri correpsondant au premier motif mi cohérent avec expr.
Exemples
1. Conversion d'une couleur en string
# let string_of_couleur c =
match c with
| Carreau -> "carreau"
| Coeur
-> "coeur"
| Pique
-> "pique"
| Trefle -> "trèfle" ;;
val string_of_couleur : couleur -> string = <fun>
# List.map string_of_couleur [Trefle; Carreau; Pique ; Coeur] ;;
- : string list = ["trèfle"; "carreau"; "pique"; "coeur"]
2. Le ou-exclusif
# let xor a b =
match (a,b) with
| (true,true)
-> false
| (true,false) -> true
| (false,true) -> true
| (false,false) -> false ;;
val xor : bool -> bool -> bool = <fun>
Nécessité d'un ltrage exhaustif
Un ltrage doit être exhaustif, c'est-à-dire envisager tous les cas.
S'il ne l'est pas, l'interprète s'empresse de le signaler par un avertissement.
# let xor a b =
match (a,b) with
| (true,true) -> false
| (true,false) -> true
| (false,true) -> true ;;
Characters 15-102:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
(false, false)
match (a,b) with
| (true,true) -> false
| (true,false) -> true
| (false,true) -> true...
val xor : bool -> bool -> bool = <fun>
Motif universel Supposons que nous voulions écrire une fonction chiffre : char -> bool qui teste
si le caractère passé en paramètre est un chire. Il serait pénible d'écrire cette fonction par ltrage de
motif en examinant chacun des 256 caractères. Il est alors judicieux d'utiliser le motif universel _ satisfait
par toutes les données.
3.4.
FILTRAGE DE MOTIF
23
# let chiffre c =
match c with
’0’ | ’1’ | ’2’ | ’3’ | ’4’
| ’5’ | ’6’ | ’7’ | ’8’ | ’9’ -> true
| _ -> false ;;
val chiffre : char -> bool = <fun>
# List.map chiffre [’0’ ; ’A’ ; ’8’] ;;
- : bool list = [true; false; true]
Motif avec variables
Un motif peut contenir une variable.
Voici une dénition plus concise du ou-exclusif utilisant des motifs avec variable.
# let xor a b =
match (a,b) with
| (true,b) -> not b
| (_,b)
-> b ;;
val xor : bool -> bool -> bool = <fun>
Toutefois, une variable ne peut avoir qu'une seule occurrence dans un motif. Il pourrait être tentant
d'écrire l'égalité en utilisant le ltrage
# let egal x y =
match (x,y) with
| (x,x) -> true
| _ -> false ;;
Characters 38-39:
| (x,x) -> true
^
This variable is bound several times in this matching
mais il est refusé par l'interprète.
3.4.2 Filtrage de paramètres
On trouve des motifs partout :
les déclarations de variables suivent le schéma let motif =expr, comme par exemple dans
# let a,b = 1,2 ;;
val a : int = 1
val b : int = 2
la syntaxe des expressions function suit le schéma function motif -> expr, comme par exemple
dans
# let fst = function (x,y) -> x ;;
val fst : ’a * ’b -> ’a = <fun>
ou encore dans
# let rec fact = function
0 -> 1
| n -> n*fact(n-1) ;;
val fact : int -> int = <fun>
C'est surtout dans la dénition de fonctions à paramètres de type que le ltrage trouve son application.
Exemples
1. ltrage de liste
# let rec miroir = function
| []
-> []
| x::l -> miroir(l)@[x] ;;
val miroir : ’a list -> ’a list = <fun>
# miroir [1;2;3] ;;
- : int list = [3; 2; 1]
24
CHAPITRE 3.
COUPLES,
N -UPLETS
ET LISTES
2. ltrage de type somme
# let rec longueur = function
| Nil -> 0
| Cons (_,l) -> 1 + longueur l ;;
val longueur : liste_d_entiers -> int = <fun>
# longueur (Cons(1,Cons(2,Nil))) ;;
- : int = 2
3.5
Exercices
Exercice 15. Fonctions fst et snd
Écrivez les fonctions prédénies fst et snd.
Exercice 16. Accès aux composantes d'un n-uplet
Question 1. Réalisez une fonction qui donne la valeur de la deuxième composante d'un triplet.
Question 2. Réalisez une fonction qui donne la valeur de la i-ème composante d'un triplet, 1 ≤ i ≤ 3
étant passé en paramètre. Quelle contrainte cela impose-t-il sur le triplet ?
Exercice 17. Currycation décurrycation
Question 1. Quelle diérence y a-t-il entre les deux fonctions
let add x y = x+y ;;
let plus (x,y) = x+y ;;
En mathématiques, une fonction dénie sur N2 peut être indiéremment considérée comme une fonction à deux variables dans N (comme la fonction add), ou comme une fonction à une variable dans N2
(comme la fonction plus).
En informatique (et en logique), les deux fonctions sont deux formes diérentes de la même fonction :
Question 2. Déterminez le type et réalisez la fonction curryfie (resp. decurryfie) qui transforme une
fonction à un argument couple (resp. à deux arguments) en sa forme curryée (resp. décurryée).
Exercice 18. Listes et premières manipulations
Pour la session qui suit, donnez le type et la valeur de chacune des phrases
let
let
let
let
let
let
lvide = [] ;;
lpos = [1; 2; 3] ;;
lpos = 19::lpos ;;
lpos = 13::42::lpos;;
lneg = [(-1) ; (-2) ; (-3)] ;;
lneg = [(-7) ; (-8) ; (-9)] @ lpos ;;
List.hd
List.tl
List.length
List.nth
List.rev
List.mem 2
lpos
lpos
lpos
lpos
lpos
lpos
;; (* donne la tete de la liste *)
;; (* donne la liste sans sa tete *)
;;
5 ;;
;;
;; (* test d’appartenance de l’element 2 *)
let lfrag = [1 ; "chaine" ; true ];;
List.hd lvide ;; -> Uncaught exception Failure("hd")
Exercice 19. Typage de listes
Donnez le type des listes
1. []
2. [[]]
3. [[] ;[[]]]
4. [[],[[]]]
3.5.
25
EXERCICES
Exercice 20. Listes et fonctions de manipulation avancées
Pour la session qui suit, donnez le type et la valeur de chacune des phrases
let lpos = [1;8;9;5;7;3;2];;
(*
* List.find : (’a -> bool) -> a’ list -> a’ = <fun>
* recherche du premier element verifiant le predicat
*)
’a -> bool
List.find (function x -> x mod 2 = 0) lpos;;
(*
* List.find_all ou List.filter : (’a -> bool) -> a’ list -> a’ list = <fun>
* recherche de tous les elements verifiant le predicat ’a -> bool
* résultat de type ’a list
*)
List.find_all (function x ->
x mod 2 = 0) lpos;;
List.filter
(function x -> not (x mod 2 = 0)) lpos;;
(*
* List.partition :(’a -> bool) -> a’ list -> ’a list * ’a list
* partition des elements selon un predicat ’a -> bool
* type résultat ’a list * ’a list
*)
List.partition (function x -> x mod 2 = 0) lpos;;
(* List.iter : (’a -> unit) -> ’a list -> unit
* applique la fonction ’a -> unit a chacun des elements
*)
List.iter (function x -> print_string "(" ; print_int x ; print_string ")") lpos;;
(* List.map : (’a -> ’b) -> a’ list -> b’ list
* applique la fonction ’a -> ’b a chacun des elements de la liste
* résultat de type ’b list
*)
List.map
(function x -> x * x) lpos;;
(* List.flatten : ’a list list -> a’ list
* applatissement des a’ listes de listes en a’ listes
*)
List.flatten [[1;2;3];[4;5];[];[6;7]];;
List.flatten [[[1];[2;3]];[[4];[5]];[[]];[[6];[7]]];;
Exercice 21. Listes et redénition des fonctions de manipulation
Question 1. Réécrire la fonction length d'un liste en utilisant la fonction List.tl.
Question 2. Réécrire la fonction nth en utilisant les fonctions List.tl et List.hd .
Question 3. Réécrire la fonction rev à l'aide de la fonction de concatenation @ .
Question 4. Réécrire la fonction map en utilisant les fonctions List.tl et List.hd.
Question 5. Réécrire la fonction filter à l'aide des fonctions List.tl et List.hd.
Question 6. Dénissez une procédure qui ache une expression arithmétique sous forme inxée complètement parenthésée.
26
CHAPITRE 3.
COUPLES,
N -UPLETS
ET LISTES
Chapitre 4
Peut-on tout programmer avec des
fonctions ?
Le but de ces quelques notes est de montrer qu'il est possible de représenter les nombres entiers, les
booléens et les couples par des fonctions pures, et de se convaincre que tout ce qui est programmable
peut l'être avec le seul formalisme des fonctions. La base théorique sous-jacente est le λ-calcul introduit
en 1932 par Alonzo Church pour tenter de cerner la notion de calculabilité.
Tout est exprimé dans le langage
, en s'interdisant le recours aux nombres, booléens et n-uplets
de ce langage, ainsi qu'aux formes if et let rec, et les opérateurs ou fonctions prédénies. Les seuls
éléments du langage que nous nous autoriserons sont les identicateurs de variables, l'application d'une
fonction à un argument et la construction d'une fonction (forme function x -> ...). Par commodité,
nous nous autoriserons aussi la forme let pour nommer certains termes.
Caml
4.1
Les entiers
4.1.1 Représentation de Church
Observons la représentation des quatre premiers nombres entiers 0, 1, 2 et 3 donnée par les dénitions
suivantes :
(* l’entier zéro *)
let zero = fun f x -> x ;;
(* l’entier un *)
let un = fun f x -> f x ;;
(* l’entier deux *)
let deux = fun f x -> f (f x) ;;
(* l’entier trois *)
let trois = fun f x -> f (f (f x)) ;;
Chacune de ces dénitions indique que l'entier n est représenté comme une fonction qui itère une
certaine fonction f n fois depuis une donnée x.
Plus généralement, la représentation de Church d'un entier n ∈ N peut être donnée par l'expression
fun f x -> f (f ... (f x) ;;
dans laquelle f apparaît n fois.
Application : Cette représentation d'un entier n peut être utilisée pour itérer n fois une fonction f
depuis un élément x. Par exemple, en prenant pour fonction f la fonction prédénie succ et pour élément
x l'entier (de
) 0, on a
Caml
# zero succ 0 ;;
- : int = 0
27
28
CHAPITRE 4.
#
#
#
-
PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
un succ 0 ;;
: int = 1
deux succ 0 ;;
: int = 2
trois succ 0 ;;
: int = 3
Cela suggère la fonction de conversion d'un entier représenté sous forme de Church en un entier de
Caml.
let entier2int n = n succ 0 ;;
Remarque :
le type général de la représentation de Church d'un entier est
(’a -> ’a) -> ’a -> ’a.
Toutefois, le type de zero est ’a -> ’b -> ’b et celui de un est (’a -> ’b) -> ’a -> ’b. Le type
(’a -> ’a) -> ’a -> ’a est l'unicateur le plus général de ces deux types.
4.1.2 La fonction successeur
La fonction successeur doit transformer un entier n en l'entier n+1. Lorsque les entiers sont représentés
sous forme de Church, cela revient à ajouter une application supplémentaire de la fonction intervenant
dans la représentation. Cet ajout peut se faire de deux manières :
1. on applique la fonction f au résultat de l'itération
let suc1 = fun n f x -> f (n f x) ;;
2. on applique la fonction f à l'élément x avant d'appliquer n
let suc2 = fun n f x -> n f (f x) ;;
#
#
-
List.map entier2int
: int list = [1; 2;
List.map entier2int
: int list = [1; 2;
(List.map suc1 [zero; un; deux; trois]) ;;
3; 4]
(List.map suc2 [zero; un; deux; trois]) ;;
3; 4]
Remarques :
1. Le type de la fonction suc1 est ((’a -> ’b) -> ’c -> ’a) -> (’a -> ’b) -> ’c -> ’b et celui
de la fonction suc2 est ((’a -> ’b) -> ’b -> ’c) -> (’a -> ’b) -> ’a -> ’c. Chacun de ces
types est bien plus général que ((’a -> ’a) -> ’a -> ’a) -> (’a -> ’a) -> ’a -> ’a.
2. Les entiers donnés par ces deux fonctions ne sont pas de type (’a -> ’a) -> ’a -> ’a mais de
type (’_a -> ’_a) -> ’_a -> ’_a.
# let quatre1
val quatre1 :
# let quatre2
val quatre2 :
= suc1 trois
(’_a -> ’_a)
= suc2 trois
(’_a -> ’_a)
;;
-> ’_a -> ’_a = <fun>
;;
-> ’_a -> ’_a = <fun>
La variable de type ’_a indique que le type de quatre1 est en attente d'instanciation. C'est la
première utilisation de quatre1 qui xera la valeur de ’_a.
#
#
#
-
quatre1 ;;
: (’_a -> ’_a) -> ’_a -> ’_a = <fun>
quatre1 succ ;;
: int -> int = <fun>
quatre1 ;;
: (int -> int) -> int -> int = <fun>
4.1.
29
LES ENTIERS
4.1.3 L'addition
Pour dénir la somme de deux entiers n et m, on peut s'y prendre de deux façons :
1. on peut considérer que la somme n + m est obtenue en itérant n fois la fonction successeur sur m
let add1 = fun n m -> n suc1 m ;;
2. ou si on préfère ne pas utiliser la fonction successeur, la somme n+m peut être vue comme l'itération
n fois d'une fonction f sur un élément obtenu en itérant m fois la même fonction f
let add2 = fun n m f x -> n f (m f x) ;;
#
#
#
#
-
List.map entier2int
: int list = [0; 1;
List.map entier2int
: int list = [1; 2;
List.map entier2int
: int list = [2; 3;
List.map entier2int
: int list = [3; 4;
(List.map
2; 3]
(List.map
3; 4]
(List.map
4; 5]
(List.map
5; 6]
(add1 zero) [zero; un; deux; trois]) ;;
(add1 un) [zero; un; deux; trois]) ;;
(add1 deux) [zero; un; deux; trois]) ;;
(add1 trois) [zero; un; deux; trois]) ;;
4.1.4 La multiplication
Le produit de deux entiers n et m est l'itération n fois de la fonction f itérée m fois.
let mult = fun n m f -> n (m f);;
#
#
#
#
-
List.map entier2int
: int list = [0; 0;
List.map entier2int
: int list = [0; 1;
List.map entier2int
: int list = [0; 2;
List.map entier2int
: int list = [0; 3;
(List.map
0; 0]
(List.map
2; 3]
(List.map
4; 6]
(List.map
6; 9]
(mult zero) [zero; un; deux; trois]) ;;
(mult un) [zero; un; deux; trois]) ;;
(mult deux) [zero; un; deux; trois]) ;;
(mult trois) [zero; un; deux; trois]) ;;
4.1.5 L'exponentiation
L'exponentiation mn est encore plus simple à dénir puisqu'on l'obtient en itérant n fois l'entier m.
let exp = fun n m -> n m;;
#
#
#
#
-
List.map entier2int
: int list = [1; 1;
List.map entier2int
: int list = [0; 1;
List.map entier2int
: int list = [0; 1;
List.map entier2int
: int list = [0; 1;
(List.map
1; 1]
(List.map
2; 3]
(List.map
4; 9]
(List.map
8; 27]
(exp zero) [zero; un; deux; trois]) ;;
(exp un) [zero; un; deux; trois]) ;;
(exp deux) [zero; un; deux; trois]) ;;
(exp trois) [zero; un; deux; trois]) ;;
4.1.6 Et la soustraction ?
La soustraction va nécessiter de dénir les booléens et les couples.
30
CHAPITRE 4.
4.2
PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
Les booléens
4.2.1 Représentation des deux booléens
Nous dénirons les deux valeurs booléennes par
(* Le vrai *)
let vrai = fun x y -> x ;;
(* le faux *)
let faux = fun x y -> y ;;
Ces dénitions se justieront par l'usage que nous en ferons pour dénir la conditionnelle.
La fonction booleen2bool convertit les termes vrai et faux en les booléens de
.
Caml
let booleen2bool b = b true false ;;
Remarque :
Le booléen faux est représenté par le même terme que l'entier 0.
Remarque :
le type de vrai est ’a -> ’b -> ’a et celui de faux ’a -> ’b -> ’b. Le type le plus
général qui unie ces deux types est ’a -> ’a -> ’a.
4.2.2 La conditionnelle
La fonction conditionnelle permet d'exprimer la forme si ... alors ... sinon .... Le choix de la
représentation des booléens rend très aisée la dénition de cette fonction.
(* la fonction conditionnelle
* cond c a s = a si c est vrai
= s sinon
*
)
*
let cond = fun c a s -> c a s ;;
#
#
-
cond vrai 1 2;;
: int = 1
cond faux 1 2;;
: int = 2
Remarque : s'il est facile de dénir la fonction conditionnelle, son utilisation pose néanmoins le problème de l'évaluation d'une expression conditionnelle. Si pour évaluer un appel de fonction, on emploie
la stratégie d'évaluation préalable de tous ses arguments, il peut arriver certaines dicultés.
adopte l'évaluation complète des arguments d'un appel de fonction et on comprend pourquoi l'évaluation
de l'expression cond vrai 1 1/0, qui vaut 1, pose problème.
OCaml
# cond vrai 1 1/0;;
Exception: Division_by_zero.
Pour éviter ce genre de problème, il est nécessaire d'adopter une autre stratégie nommée évaluation
paresseuse qui consiste à n'évaluer les expressions que lorsqu'on y est obligé.
4.2.3 Les opérateurs logiques
La fonction cond permet une dénition aisée des opérateurs booléens.
(* l’opérateur de négation *)
let non = function b -> cond b faux vrai ;;
(* la conjonction *)
let et = fun b1 b2 -> cond b1 b2 faux ;;
(* la disjonction *)
let ou = fun b1 b2 -> cond b1 vrai b2 ;;
4.3.
31
LES COUPLES
#
#
#
#
#
-
List.map booleen2bool (List.map
: bool list = [false; true]
List.map booleen2bool (List.map
: bool list = [true; false]
List.map booleen2bool (List.map
: bool list = [false; false]
List.map booleen2bool (List.map
: bool list = [true; true]
List.map booleen2bool (List.map
: bool list = [true; false]
non [vrai ; faux]) ;;
(et vrai) [vrai ; faux]) ;;
(et faux) [vrai ; faux]) ;;
(ou vrai) [vrai ; faux]) ;;;
(ou faux) [vrai ; faux]) ;;;
4.2.4 Un test de nullité
On est en mesure maintenant de dénir le test de nullité d'un entier.
let estNul = function n -> n (function x -> faux) vrai ;;
# List.map booleen2bool (List.map estNul [zero; un; deux; trois]) ;;
- : bool list = [true; false; false; false]
4.3
Les couples
Par dénition, un couple contient deux choses. On doit pouvoir construire un couple à partir de
n'importe quelles deux choses, et extraire l'une ou l'autre des choses contenues dans un couple.
Le couple (x, y) peut être représenté par la fonction function z -> z x y.
La conversion de ces couples en couples de
s'obtient avec la fonction
Caml
let couple2caml = function c -> (c vrai),(c faux) ;;
Remarque :
le type le plus général d'un couple est donc (’a -> ’b -> ’c) -> ’c.
4.3.1 Le constructeur
Le constructeur de couple est la fonction cons.
let cons = fun x y -> function z -> z x y ;;
#
#
-
cons 1 2;;
: (int -> int -> ’_a) -> ’_a = <fun>
couple2caml (cons 1 2) ;;
: int * int = (1, 2)
Remarque :
le champ de notre étude est indépendant de la notion de type (c'est du λ-calcul pur).
Cependant, l'utilisation de
amène quelques restrictions d'usage de la fonction couple2caml.
OCaml
# cons 1 true ;;
- : (int -> bool -> ’_a) -> ’_a = <fun>
# couple2caml (cons 1 true) ;;
Characters 12-25:
couple2caml (cons 1 true) ;;
^^^^^^^^^^^^^
This expression has type (int -> bool -> ’a) -> ’a but is here used with type
(int -> int -> int) -> ’b
32
CHAPITRE 4.
PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
4.3.2 Les sélecteurs
Les sélecteurs des composantes d'un couple s'obtiennent aisément avec les booléens.
(* sélection de la première composante *)
let car = function c -> c vrai ;;
(* sélection de la seconde composante *)
let cdr = function c -> c faux ;;
#
#
-
entier2int (car (cons un deux)) ;;
: int = 1
entier2int (cdr (cons un deux)) ;;
: int = 2
4.3.3 Prédécesseur d'un entier
Avec les couples il est possible de calculer le prédécesseur d'un entier n. Il sut d'itérer n fois la
fonction qui à un couple (a, b) associe le couple (b, b + 1) à partir du couple (0, 0).
(* prédécesseur
* pred n = entier qui précède n
*)
let pred = function n ->
car (n (function c -> cons (cdr c) (suc1 (cdr c))) (cons zero zero));;
# List.map entier2int (List.map pred [zero; un; deux; trois]) ;;
- : int list = [0; 0; 1; 2]
Remarque :
Avec cette dénition, zero est son propre prédécesseur.
4.3.4 Soustraction
La soustraction peut se dénir par itération du prédécesseur.
let sub = fun n m -> m pred n ;;
4.3.5 Test d'égalité
Une idée naturelle pour décrire la fonction d'égalité de deux entiers est de tester la nullité de la
diérence de ces deux entiers. Mais cette idée ne peut s'appliquer en utilisant la fonction sub car elle
attribue la valeur zero à n − m lorsque n ≤ m.
let inf = fun n m -> estNul (sub n m) ;;
let egal = fun n m -> et (inf n m) (inf m n) ;;
4.4
La récursivité
4.4.1 Factorielle avec des couples
On peut calculer n! en itérant n fois la fonction (a, b) 7→ (a + 1, (a + 1)b) depuis le couple (0, 1).
let fact = function n ->
cdr (n (function c -> (cons (suc1 (car c)) (mult (suc1 (car c)) (cdr c) )))
(cons zero un)) ;;
#
#
-
List.map entier2int (List.map fact [zero; un; deux; trois]) ;;
: int list = [1; 1; 2; 6]
entier2int (fact (exp deux trois)) ;;
: int = 362880
4.4.
33
LA RÉCURSIVITÉ
4.4.2 Point xe
On imagine bien pouvoir écrire, de la même façon que la fonction fact ci-dessus, toute fonction
que l'on programmerait autrement à l'aide d'une boucle pour1 . Mais qu'en estil pour les fonctions qui
n'entrent pas dans ce cadre2 ?
Nous allons proposer une autre solution pour la fonction factorielle qui suit un schéma général applicable à toute fonction. Cette approche est celle du calcul du plus petit point xe d'une fonctionnelle,
point xe qui peut être déterminé par un opérateur nommé combinateur de point xe.
L'ensemble ordonné F
Notons F = NN l'ensemble de toutes les fonctions de N dans N. Les fonctions considérées ici ne sont
pas nécessairement dénies sur N tout entier, c'estàdire qu'étant donné f ∈ F , il peut exister des
valeurs de n ∈ N telles que f (n) ne soit pas déni. Nous noterons ⊥ la (seule) fonction de F dénie nulle
part.
3 la fonction ⊥ :
Voici écrite en
Caml
let bottom = function x ->
(function y -> y y) (function y -> y y);;
Toute tentative d'application de la fonction bottom à un quelconque argument conduit à une évaluation
innie.
# bottom 1 ;;
Interrupted.
On dira qu'une fonction f ∈ F est moins dénie qu'une fonction g ∈ F , et on notera f 4 g , si
pour tout entier n où la fonction f est dénie, on a f (n) = g(n), autrement dit si f et g coincident sur
l'ensemble de dénition de f . La relation 4 sur l'ensemble F en fait un ensemble non totalement ordonné
admettant la fonction ⊥ comme plus petit élément, et les fonctions partout dénies comme éléments
maximaux.
Fonctionnelles
Nous appellerons fonctionnelle toute application de F dans lui-même. Par exemple, la fonctionnelle
Φfact dénie par
Φfact : F
−→
F
f
7−→
g = Φfact (f )
où g est la fonction dénie par
g(n) =
1 si n = 0
.
n × f (n − 1) sinon
Appliquons Φfact à la fonction ⊥
f1 = Φfact (⊥).
Par la dénition même de Φfact , la fonction f1 est dénie en 0 et vaut 1. Et elle n'est dénie nulle part
ailleurs, étant donné que ⊥ n'est dénie nulle part.
Appliquons maintenant Φfact à f1
f2 = Φfact (f1 ).
Cette fonction f2 est dénie en 0 où elle prend la même valeur que f1 , et en 1 où elle vaut 1. Ainsi f1 est
moins dénie que f2 . D'autre part, f2 n'est dénie pour aucun entier supérieur à 1.
Si on considère la suite de fonctions (fn )n∈N de F dénie par récurrence par
f0
=
⊥
fn+1
=
Φfact (fn )
on obtient des fonctions telles que
1 Dans la terminomogie des fonctions récursives, ce sont les fonctions primitives récursives
2 Comme par exemple, le calcul du pgcd de deux entiers, ou encore le calcul de la longueur d'une liste.
3 Par défaut,
refuse la dénition de bottom car l'expression y y n'est pas typable. Il faut autoriser
OCaml
les types
récursifs (option -rectypes de la commande ocaml) pour que Caml accepte cette dénition en lui donnant alors le type ’a
-> ’b.
34
CHAPITRE 4.
PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
1. fn (k) = k! si k < n ;
2. et fn (k) non déni si k ≥ n.
On peut remarquer que cette suite est constituée de fonctions de mieux en mieux dénies
f0 4 f1 4 f2 4 . . . 4 fn 4 . . .
autrement dit la suite est croissante dans F , et que la fonction factorielle (fact) est mieux dénie que
toutes les fonctions de cette suite,
∀n ∈ N fn 4 fact.
On peut dénir tout cela en
Caml4 par
let phiFact = fun f n ->
if n=0 then 1 else n * f (n - 1) ;;
let f0 = bottom ;;
let f1 = phiFact f0 ;;
let f2 = phiFact f1 ;;
let f3 = phiFact f2 ;;
let f4 = phiFact f3 ;;
et on peut vérier que les fonctions ainsi dénies sont des fonctions qui approchent de mieux en mieux
la fonction factorielle.
# List.map f2 [0;1] ;;
- : int list = [1; 1]
# List.map f3 [0;1;2] ;;
- : int list = [1; 1; 2]
# List.map f4 [0;1;2;3] ;;
- : int list = [1; 1; 2; 6]
# f4 4 ;;
Interrupted.
Point xe
Quelle est la fonction obtenue si on applique la fonctionnelle Φfact à la fonction fact ? On peut vérier
que l'on a
Φfact (fact) = fact.
Trichons un peu et vérions cette égalité :
# let rec fact n =
if n=0 then 1
else n*fact(n-1) ;;
val fact : int -> int = <fun>
# List.map fact [1;2;3;4;5;6;7;8;9;10] ;;
- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]
# let fact2 = phiFact fact ;;
val fact2 : int -> int = <fun>
# List.map fact2 [1;2;3;4;5;6;7;8;9;10] ;;
- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]
Autrement dit, la fonction fact est un point xe de la fonctionnelle Φf act .
Une fonction f ∈ F est un point xe d'une fonctionnelle Φ si on a l'égalité
Φ(f ) = f.
4 Nous abandonnons les contraintes xées au début de ce document an de permettre une meilleure lecture du code et
faciliter la compréhension.
4.5.
35
EXERCICES
Remarque :
Deux questions naturelles se posent
1. est-ce que toute fonctionnelle admet des points xes ?
2. y a-t-il unicité du point xe ?
La réponse à ces deux questions est négative en général. L'existence est assurée si la fonctionnelle satisfait
une propriété de monotonie, c'estàdire si Φ(f ) 4 Φ(g) dès lors que f 4 g 5 . Φfact est une fonctionnelle
monotone, et la fonction fact est donc son (plus petit) point xe.
Combinateur de point xe
Existe-t-il un moyen de calculer le point xe d'une fonctionnelle (s'il existe). La réponse est armative,
et il existe plusieurs opérateurs (ou fonctions ou algorithmes) permettant de les calculer. On les appelle
combinateurs de point xe.
En voici un, nommé combinateur de Curry, dont nous donnons la dénition en
.
Caml
let fixCurry = function f ->
(function x -> f (x x)) (function x -> f (x x)) ;;
Vérions que fixCurry est un combinateur de point xe. Soit Φ une fonctionnelle.
fixCurry Φ
=
→
→
=
(function f -> (function x -> f (x x)) (function x -> f (x x))) Φ
(function x -> Φ (x x)) (function x -> Φ (x x))
Φ ((function x -> Φ (x x)) (function x -> Φ (x x)))
Φ (fixcurry Φ).
La èche → indique une étape de réduction.
Ainsi, le terme fixCurry Φ est un point xe de Φ.
Modions la dénition précédente de fixCurry pour le rendre opérationnel en
Caml.
let fixCurry = function f ->
(fun x y -> f (x x) y) (fun x y -> f (x x) y) ;;
Appliquons ce combinateur à Φfact
let fact = fixCurry phiFact ;;
et vérions le point xe obtenu
# List.map fact [1;2;3;4;5;6;7;8;9;10] ;;
- : int list = [1; 2; 6; 24; 120; 720; 5040; 40320; 362880; 3628800]
Exercice 22.
Dénir de façon analogue les fonctions
1. bonacci
2. pgcd de deux entiers
3. longueur d'une liste
4. concaténation de deux listes.
4.5
5 pour
Exercices
en savoir plus à ce sujet cf A COMPLETER
36
CHAPITRE 4.
PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
Bibliographie
[Cha00] Emmanuel Chailloux, Pascal Manoury et Bruno Pagano Développement d'applications avec
Objective Caml O'Reilly, 2000
[Narb05] Philippe Narbel Programmation fonctionnelle, générique et objet Vuibert, 2005
37
38
BIBLIOGRAPHIE
Table des matières
1 Programmation fonctionnelle
1.1
1.2
1.3
Programmation fonctionnelle . . . . .
1.1.1
. . . . . . . . . . . . . .
Premiers pas . . . . . . . . . . . . . .
1.2.1 Phrases du langage . . . . . . .
1.2.2 Types de base . . . . . . . . . .
1.2.3 Déclaration de variables . . . .
1.2.4 Déclaration locale de variables
1.2.5 Expression conditionnelle . . .
1.2.6 Fonctions . . . . . . . . . . . .
1.2.7 Déclarations récursives . . . . .
Exercices . . . . . . . . . . . . . . . .
Caml
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2 Ordre supérieur - Polymorphisme
2.1
2.2
2.3
Ordre supérieur . . . . . . . . . . . . . . . . .
2.1.1 Les fonctions d'arité supérieure à 1 . .
2.1.2 Fonctionnelles . . . . . . . . . . . . . .
Polymorphisme . . . . . . . . . . . . . . . . .
2.2.1 Fonctions polymorphes . . . . . . . . .
2.2.2 Typage d'une expression fonctionnelle
2.2.3 Typage des fonctions récursives . . . .
2.2.4 Fonctions polymorphes . . . . . . . . .
Exercices . . . . . . . . . . . . . . . . . . . .
3 Couples, n-uplets et listes
3.1
3.2
3.3
3.4
3.5
Couples, n-uplets . . . . . . . . . . . . . . . .
3.1.1 Construction de n-uplets : . . . . . . .
3.1.2 Accès aux composantes d'un n-uplet :
3.1.3 Retour sur l'arité des fonctions . . . .
Listes . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Construction de listes . . . . . . . . .
3.2.2 Le type list . . . . . . . . . . . . . .
3.2.3 Fonctions du module List . . . . . . .
Types somme . . . . . . . . . . . . . . . . . .
3.3.1 Déclaration de types . . . . . . . . . .
3.3.2 Constucteurs constants . . . . . . . .
3.3.3 Constructeurs non constants . . . . .
3.3.4 Types récursifs . . . . . . . . . . . . .
Filtrage de motif . . . . . . . . . . . . . . . .
3.4.1 L'expression match ... with . . . . .
3.4.2 Filtrage de paramètres . . . . . . . . .
Exercices . . . . . . . . . . . . . . . . . . . .
39
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
4
4
4
6
7
7
8
9
9
11
11
11
12
12
12
13
14
14
14
17
17
17
17
18
18
18
19
19
20
20
20
21
21
21
22
23
24
40
TABLE DES MATIÈRES
4 Peut-on tout programmer avec des fonctions ?
4.1
4.2
4.3
4.4
4.5
Les entiers . . . . . . . . . . . . . . . . . .
4.1.1 Représentation de Church . . . . .
4.1.2 La fonction successeur . . . . . . .
4.1.3 L'addition . . . . . . . . . . . . . .
4.1.4 La multiplication . . . . . . . . . .
4.1.5 L'exponentiation . . . . . . . . . .
4.1.6 Et la soustraction ? . . . . . . . . .
Les booléens . . . . . . . . . . . . . . . . .
4.2.1 Représentation des deux booléens .
4.2.2 La conditionnelle . . . . . . . . . .
4.2.3 Les opérateurs logiques . . . . . .
4.2.4 Un test de nullité . . . . . . . . . .
Les couples . . . . . . . . . . . . . . . . .
4.3.1 Le constructeur . . . . . . . . . . .
4.3.2 Les sélecteurs . . . . . . . . . . . .
4.3.3 Prédécesseur d'un entier . . . . . .
4.3.4 Soustraction . . . . . . . . . . . .
4.3.5 Test d'égalité . . . . . . . . . . . .
La récursivité . . . . . . . . . . . . . . . .
4.4.1 Factorielle avec des couples . . . .
4.4.2 Point xe . . . . . . . . . . . . . .
Exercices . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
27
27
27
28
29
29
29
29
30
30
30
30
31
31
31
32
32
32
32
32
32
33
35
Téléchargement