Programmation Fonctionnelle Compress
Programmation Fonctionnelle Compress
Programmation fonctionnelle
octobre 2008
2
Chapitre 1
Programmation fonctionnelle
1.1.1 Caml
Langage utilisé : OCaml , dialecte de la famille ML
. 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.
3
4 CHAPITRE 1. PROGRAMMATION FONCTIONNELLE
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).
# 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
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. PREMIERS PAS 5
Opérateur signication
+ addition
- soustraction
* multiplication
/ division entière
mod reste de la division entière
oat : les nombres ottants. Ils respectent la norme IEEE 754. OCaml 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 signication
float_of_int conversion d'un int en float
ceil partie entière supérieure (plafond)
floor partie entière inférieure (plancher)
sqrt racine carrée
exp exponentielle
log logarithme népérien
log10 logarithme décimal
... ...
string : les chaînes de caractères (longueur limitée à 264 − 6).
Opérateur signication
^ concaténation
Fonction signication
string_of_int conversion d'un int en un string
int_of_string conversion d'un string en un int
string_of_float conversion d'un float en un string
float_of_string conversion d'un string en un float
String.length longueur
char : 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 signication
not négation
bool : les booléens pouvant prendre deux valeurs true et false. && ou & et séquentiel
|| ou or 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.
6 CHAPITRE 1. PROGRAMMATION FONCTIONNELLE
Opérateur signication
= égalité (structurelle)
<> négation de =
< inférieur
<= inférieur ou égal
> supérieur
>= supérieur ou égal
unit : 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 OCaml, 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
# 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 = ()
où < nom > est le nom de la variable (en OCaml 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
...
and <nomn > = <exprn > ;;
Cette forme syntaxique permet de déclarer la variable nommée < nom > dans l'environnement d'éval-
uation 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
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 :
en OCaml , 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 OCaml , 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
En OCaml , 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
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>
# carre 2 ;;
- : int = 4
# carre (2 + 2) ;;
- : int = 16
est acceptable en Caml parce que l'expression (v) a pour valeur celle de v.
1.3. EXERCICES 9
# (2);;
- : int = 2
# carre (2);;
- : int = 4
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'envi-
ronnement 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
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
En OCaml , 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"
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
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.
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 Caml , toutes les fonctions sont d'arité 1.
Toute déclaration de la forme
let f x1 x2 ... 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
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 :
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 .
# demiValeurEnZero carre ;;
- : int = 0
# demiValeurEnZero (add 2);;
- : int = 1
2.2 Polymorphisme
# 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.
et calculons son type dans l'environnement Env =< a : int = 1 > .Env0 où Env0 est l'environnement
initial standard deOCaml .
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.
# (function x -> x) 1 ;;
- : int = 1
# (function x -> x) true ;;
- : bool = true
# (function x -> x) "elfe" ;;
- : string = "elfe"
dans un environnement Env , il sut de déterminer les contraintes sur les inconnues σ et τ dans l'envi-
ronnement augmenté
Env 0 =< f : σ → τ = x 7→< expr >> .Env
2.3 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. EXERCICES 15
Exercice 9. Sommes
Question 1. Réalisez une fonction somme1 paramétrée par les entiers a et b qui calcule bk=a k.
P
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 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)
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.
17
18 CHAPITRE 3. COUPLES, N -UPLETS ET LISTES
Les deux fonctions add et plus sont diérentes bien que mathématiquement semblables. On dit que
add est la forme 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.
3.2 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)]
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 = []
tl : ’a list -> ’a list donne la liste sans son premier élément. CU : la liste ne doit pas être
vide.
20 CHAPITRE 3. COUPLES, N -UPLETS ET LISTES
# List.tl [1;2;3;4] ;;
- : int list = [2; 3; 4]
# List.tl [] ;;
Exception: Failure "tl".
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.
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]
Syntaxe :
- : couleur = Trefle
# Pique, Carreau ;;
- : couleur * couleur = (Pique, Carreau)
# [Trefle; Coeur; Carreau] ;;
- : couleur list = [Trefle; Coeur; Carreau]
Les types ainsi dénis sont les analogues de types énumérés de langages comme Ada Pascal
, , . . . Ils
n'ont qu'un nombre ni de valeurs correspondant à chacun des constructeurs utilisés dans l'énumération.
Le type entier_ou_booleen ainsi déclaré réunit en un seul type le type bool et le type int. Les con-
structeurs Entier et Booleen prennent chacun un argument qui est un type placé après le mot-clé of.
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))
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]
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
3.5 Exercices
En mathématiques, une fonction dénie sur N2 peut être indiéremment considérée comme une fonc-
tion à 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 lvide = [] ;;
let lpos = [1; 2; 3] ;;
let lpos = 19::lpos ;;
let lpos = 13::42::lpos;;
let lneg = [(-1) ; (-2) ; (-3)] ;;
let lneg = [(-7) ; (-8) ; (-9)] @ lpos ;;
(*
* List.find : (’a -> bool) -> a’ list -> a’ = <fun>
* recherche du premier element verifiant le predicat ’a -> bool
*)
(*
* 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.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.flatten [[1;2;3];[4;5];[];[6;7]];;
List.flatten [[[1];[2;3]];[[4];[5]];[[]];[[6];[7]]];;
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 Caml , 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.
(* 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) ;;
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 (deCaml ) 0, on a
# 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 ;;
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 = suc1 trois ;;
val quatre1 : (’_a -> ’_a) -> ’_a -> ’_a = <fun>
# let quatre2 = suc2 trois ;;
val quatre2 : (’_a -> ’_a) -> ’_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. LES ENTIERS 29
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
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
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.
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.
4.1.6 Et la soustraction ?
La soustraction va nécessiter de dénir les booléens et les couples.
30 CHAPITRE 4. PEUT-ON TOUT PROGRAMMER AVEC DES FONCTIONS ?
(* 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 ;;
Remarque : s'il est facile de dénir la fonction conditionnelle, son utilisation pose néanmoins le prob-
lè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. OCaml
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.
# 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é.
(* la conjonction *)
let et = fun b1 b2 -> cond b1 b2 faux ;;
(* la disjonction *)
let ou = fun b1 b2 -> cond b1 vrai b2 ;;
4.3. LES COUPLES 31
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 Caml s'obtient avec la fonction
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.
# 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 deOCaml amène quelques restrictions d'usage de la fonction couple2caml.
# 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.4 Soustraction
La soustraction peut se dénir par itération du prédécesseur.
let sub = fun n m -> m pred n ;;
4.4 La récursivité
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.
Voici écrite en Caml3 la fonction ⊥ :
Toute tentative d'application de la fonction bottom à un quelconque argument conduit à une évaluation
innie.
# bottom 1 ;;
Interrupted.
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
1 si n = 0
g(n) = .
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, OCaml refuse la dénition de bottom car l'expression y y n'est pas typable. Il faut autoriserles 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.
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.
Φ(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. EXERCICES 35
Vérions que fixCurry est un combinateur de point xe. Soit Φ une fonctionnelle.
4.5 Exercices
[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 3
1.1 Programmation fonctionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Premiers pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1 Phrases du langage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2 Types de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.3 Déclaration de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.4 Déclaration locale de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.5 Expression conditionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.6 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.7 Déclarations récursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
39
40 TABLE DES MATIÈRES