Introduction Introduction `a la programmation fonctionnelle en OCAML

publicité
Programmation Fonctionnelle
Stefano Guerrini
[email protected]
http://www.lipn.univ-paris13.fr/~guerrini
Introduction
LIPN - Institut Galilée, Université Paris Nord 13
Licence Info 3
février 2013
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
1 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Introduction à la programmation fonctionnelle et à OCaml
février 2013
2 / 131
Introduction
Styles de programmation
Style applicatif
I
I
I
Introduction à la programmation
fonctionnelle en OCAML
I
Fondé sur l’évaluation d’expressions, où le résultat ne dépend que de la valeurs
des arguments (et non de l’état de la mémoire).
Donne des programmes courts, faciles à comprendre.
Usage intensif de la récursion.
Langages typiques : Lisp, ML, Caml (Camlight, Ocaml).
Style impératif
I
I
I
I
Fondé sur l’exécution d’instructions modifiant l’état de la mémoire.
Utilise une structure de contrôle et des structures de données.
Usage intensif de l’itération.
Langages typiques : Fortran, C, Pascal.
Style objet
I
I
I
I
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
3 / 131
Un programme est vu comme une communauté de composants autonomes
(objets) disposant de ses ressources et de ses moyens d’interaction.
Utilise des classes pour décrire les structures et leur comportement.
Usage intensif de l’échange de message (métaphore).
Langages typiques : Simula, Smalltalk, C++, Java.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
4 / 131
Introduction à la programmation fonctionnelle et à OCaml
Introduction
Introduction à la programmation fonctionnelle et à OCaml
Introduction à OCaml
La petite histoire
Domaine d’utilisation du langage OCaml
OCaml (Objective Caml) est le fruit de développements continus :
Un langage d’usage général avec des domaines de prédilection.
1975 Robin Milner propose ML comme métalangage (langage de script)
pour l’assistant de preuve LCF. Il devient rapidement un langage de
programmation à part entière.
1985 Développement de Caml à l’INRIA et en parallèle, de Standard ML
à Édimbourg, de “SML of New-Jersey”, de Lazy ML à Chalmers,
de Haskell à Glasgow, etc.
1996 Objets et classes (OCaml)
I
2001 Arguments étiquetés et optionnels, variantes.
I
2002 Méthodes polymorphes, librairies partagées, etc.
I
I
2003 Modules récursifs, private types, etc.
février 2013
5 / 131
Unison
MLdonkey (peer-to-peer)
Libre cours (Site WEB)
Lindows (outils système pour une distribution de Linux)
S. Guerrini (LIPN - Paris 13)
Introduction à OCaml
Programmation Fonctionnelle
Introduction à la programmation fonctionnelle et à OCaml
Quelques mots clés
février 2013
6 / 131
Introduction à OCaml
Principales caractéristiques d’OCaml (1/3)
Langage fonctionnel
Le langage OCaml est :
fonctionnel : les fonctions sont des valeurs de première classe, elles peuvent
être retournées en résultat et passées en argument à d’autres fonctions.
I
I
à gestion mémoire automatique (comme JAVA)
fortement typé : le typage garantit l’absence d’erreur (de la machine) à
l’exécution.
I
I
compilé ou interactif (mode interactif = grosse calculette)
Le langage est typé statiquement.
Il possède un système d’inférence de types : le programmeur n’a besoin de
donner aucune information de type : les types sont synthétisés
automatiquement par le compilateur.
Le polymorphisme paramétrique
De plus le langage possède :
I
une couche à objets assez sophistiquée,
I
un système de modules très expressif.
Programmation Fonctionnelle
Une seule notion fondamentale : la notion de valeur. Les fonctions elles-mêmes
sont considérées comme des valeurs.
Un programme est essentiellement constitué d’expressions et le calcul consiste
en l’évaluation de ces expressions.
Le système de typage
avec synthèse de types : les types sont facultatifs et synthétisés par le
système.
S. Guerrini (LIPN - Paris 13)
Startups, CEA, EDF, France Telecom, Simulog,... Gros Logiciels
Quelques succès : Coq, Ensemble, ASTREE, (voir la bosse d’OCaml)
Plus récemment, outils système
1995 Compilateur vers du code natif + système de modules
Introduction à la programmation fonctionnelle et à OCaml
Calcul symbolique : Preuves mathématiques, compilation, interprétation,
analyses de programmes.
Prototypage rapide. Langage de script. Langages dédiés. Programmation
distribuée (bytecode rapide).
Enseignement et recherche
Industrie
I
1990 Implantation de Caml-Light par X. Leroy and D. Doligez
Programmation Fonctionnelle
I
I
1981 Premières implantations de ML
S. Guerrini (LIPN - Paris 13)
Domaines de prédilection
février 2013
7 / 131
Un type peut être non entièrement déterminé. Il est alors polymorphe.
Ceci permet de développer un code générique utilisable pour di↵érentes
structures de données tant que la représentation exacte de cette structure n’a
pas besoin d’être connue dans le code en question.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
8 / 131
Introduction à la programmation fonctionnelle et à OCaml
Introduction à OCaml
Introduction à la programmation fonctionnelle et à OCaml
Principales caractéristiques d’OCaml (2/3)
Introduction à OCaml
Principales caractéristiques d’OCaml (3/3)
Mécanismes particuliers
I
I
I
I
pattern matching : OCaml possède un mécanisme de programmation par cas’
très puissant.
OCaml possède un mécanisme d’exceptions pour interrompre l’évaluation
d’une expression. Ce mécanisme peut aussi être adopté comme style de
programmation.
Il y a des types récursifs mais pas de pointeurs.
La gestion de la mémoire est di↵érente de celle du C. OCaml intègre un
mécanisme de récupération automatique de la mémoire.
Environnement de programmation
I
F
F
Autres aspects de OCaml
I
I
I
I
I
I
OCaml possède des aspects impératifs. La programmation impérative est
possible mais non souhaitée.
OCaml interagit avec le C.
OCaml possède des aspects objets. Les fonctions peuvent manipuler des objets.
Les programmes peuvent être structurés en modules paramétrés (séparation du
code et de l’interface).
OCaml exécute des processus léger (threads).
OCaml communique avec le réseau internet.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Introduction à la programmation fonctionnelle et à OCaml
février 2013
9 / 131
OCaml dispose de trois modes d’exécution.
F
I
le mode interactif : c’est une boucle d’interaction. La boucle d’interaction
d’OCaml utilise du code-octet pour exécuter les phrases qui lui sont envoyées.
compilation vers du code-octet (bytecode) interprété par une machine virtuelle
(comme Java).
compilation vers du code machine exécuté directement par un microprocesseur.
OCaml dispose de nombreuses bibliothèques de programmes.
S. Guerrini (LIPN - Paris 13)
Modes d’utilisation de OCaml
Programmation Fonctionnelle
Introduction à la programmation fonctionnelle et à OCaml
Compilation
février 2013
10 / 131
Modes d’utilisation de OCaml
Mode interactif
Commandes
I
I
I
I
I
ocaml : lance la boucle d’interaction
ocamlrun : interprète de code-octet
ocamlc : compilateur en ligne de code-octet
ocamlopt : compilateur en ligne de code natif
ocamlmktop : constructeur de nouvelles boucles d’interaction
Unités de compilation : c’est la plus petite décomposition d’un programme
pouvant être compilée.
I
I
Pour la boucle d’interaction, c’est une phrase du langage. On va voir plus loin
ce qu’est une phrase.
Pour les compilateurs en ligne, c’est un couple de fichiers (le source .ml et
l’interface facultative .mli).
Extensions des fichiers objets
I
I
I
L’option -c indique au compilateur de n’engendrer que le code objet.
L’option -custom de ocamlrun permet de créer un code autonome il contient
le code-octet du programme plus l’interprète du code-octet).
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
I
I
I
I
I
I
Les fichiers objets en code-octet ont pour extension .cmo, .cma (bibliothèque
objet), .cmi (interface compilée).
Les fichiers objets en code natif ont les extensions .cmx et .cmxa.
Options du compilateur
I
L’option -I catalogue permet d’indiquer un chemin dans lequel se fera la
recherche de fichiers sources ou compilés.
La boucle possède plusieurs directives de compilation qui permettent de
modifier de façon interactif son comportement. Ces directives commencent
par # et se terminent par ;;.
février 2013
11 / 131
I
I
I
#quit;; sort de la boucle d’interaction
#directory catalogue;; ajoute un chemin de recherche
#cd catalogue;; change de catalogue courant
#load "fichier objet";; charge un fichier .cmo
#use "fichier source";; compile et charge un fichier .ml ou .mli
#print depth profondeur;; modifie la profondeur d’affichage
#trace fonction;; trace les arguments d’une fonction
#untrace fonction;; enlève cette trace
#untrace all;; enlève toute les traces
On peut lancer la boucle d’interaction sous emacs par M-x run-caml.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
12 / 131
Introduction à la programmation fonctionnelle et à OCaml
Modes d’utilisation de OCaml
Premiers pas en OCaml
Les phrases
Les phrases
Une phrase est une suite de caractères qui se termine par ;;.
Il y a essentiellement quatre genres de phrases :
Premiers pas en OCaml
I
I
I
I
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Premiers pas en OCaml
février 2013
13 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Premiers pas en OCaml
février 2013
14 / 131
Les expressions
Les opérateur arithmétiques
Une fois entré dans la boucle OCaml on peut évaluer des expressions.
Sur les entier on a les opérateurs arithmétiques infixés
shell $ ocaml
--> appel de la boucle
Objective Caml version 3.11.1
# 3;;
--> expression à évaluer
- : int = 3
--> réponse
# #quit;;
--> pour sortir de la boucle
shell $
--> shell de système
# 3 + 4 * 2;;
- : int = 11
Mais attention, les opérateurs sont typés et ils ne sont pas “overloaded”
# 3.0 + 2.5;;
Characters 0-3:
3.0 + 2.5;;
^^^
Error: This expression has type float but an expression was expected of type
int
La réponse de l’évaluation d’une expression est sa valeur et son type.
Les expressions plus simples sont les constants des types de base.
5.0;;
: float = 5.
true;;
: bool = true
"Hello, word!";;
: string = "Hello, word!"
S. Guerrini (LIPN - Paris 13)
expressions.
déclarations globales de valeurs.
déclarations de types.
déclarations d’exceptions.
Les expressions
Expressions constantes
#
#
#
-
Les
Les
Les
Les
Les opérateurs arithmétiques sur les réels ont des symboles distingués.
# 3.0 +. 2.0;;
- : float = 5.
Programmation Fonctionnelle
février 2013
15 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
16 / 131
Premiers pas en OCaml
Les expressions
Premiers pas en OCaml
Les opérateurs booléens et le if-then-else
Conversion de types
Pour utiliser un entier dans une expression qui contient un opérateur réel il
faut utiliser une fonction de conversion explicite
Les opérateurs booléens sont
I
I
I
#
#
#
#
#
#
-
not : négation
&& ou & : et séquentiel
|| ou or : ou séquentiel
# 3.0 +. 2;;
Characters 7-8:
3.0 +. 2;;
^
Error: This expression has type int but an expression was expected of type
float
# 3.0 +. float 2;;
- : float = 5.
not false && false;;
: bool = false
not (false && false);;
: bool = true
true = false;;
: bool = false
3 = 3;;
: bool = true
4 + 5 >= 10;;
: bool = false
2.0 *. 4.0 >= 7.0;;
: bool = true
Les opérateur de comparaison < > <= >= = sont les mêmes pour les entiers et
les réels
# 2.0 < 3.0;;
- : bool = true
I
# if (3 < 4) then 1 else 0;;
- : int = 1
# if (3 > 4) then 1 else 0;;
- : int = 0
seulement une des branches then/else est évaluée.
Programmation Fonctionnelle
Premiers pas en OCaml
# 2 < 3;;
- : bool = true
Mais attention, il faut faire des conversions de type si nécessaire
# 2.0 < 3;;
Characters 6-7:
2.0 < 3;;
^
Error: This expression has type int but an expression was expected of
type float
# 2.0 < float 3;;
- : bool = true
Expressions conditionnelles
S. Guerrini (LIPN - Paris 13)
Les expressions
février 2013
17 / 131
S. Guerrini (LIPN - Paris 13)
Les chaı̂nes de caractères
Programmation Fonctionnelle
Premiers pas en OCaml
Les chaı̂nes de caractères
février 2013
Les fonctions
Les fonctions
Literals
Les fonctions sont des expressions (fun est équivalent à function)
# "Hello, world!";;
- : string = "Hello, world!"
#
#
-
Concaténation
# "Hello, " ^ "world!";;
- : string = "Hello, world!"
fun x ->
: int ->
function
: int ->
x * 2;;
int = <fun>
x -> x *2;;
int = <fun>
La bibliothèque String
Le type d’une fonction n’est plus un type de base
Pour évaluer une fonction il faut lui donner les valeurs des arguments
# String.uppercase "Hello, world!";;
- : string = "HELLO, WORLD!"
# (fun x -> x * 2) 3;;
- : int = 6
# open String;;
# uppercase "Hello, world!";;
- : string = "HELLO, WORLD!"
# (fun f -> f (f 2));;
- : (int -> int) -> int = <fun>
L’argument d’une fonction peut être une fonction
Le résultat de l’évaluation d’une fonction peut être une autre fonction
Pour accéder aux fonctions dans une bibliothèque :
I
I
Biblio.f : pour utiliser la fonction f de la bibliothèque Biblio (noter que un
nom de bibliothèque commence par une majuscule)
open Biblio : pour accéder à toutes les fonctions de Biblio directement
avec leur nom.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
18 / 131
février 2013
19 / 131
# (fun f x -> f (f x)) (fun x -> x * x);;
- : int -> int = <fun>
En ajoutant des arguments on peut évaluer la fonction obtenue
# (fun f x -> f (f x)) (fun x -> x * x) 2;;
- : int = 16
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
20 / 131
Premiers pas en OCaml
Les noms
Premiers pas en OCaml
Les noms
Les noms
Convention sur les noms
On peut associer un nom à des expressions pour le réutiliser après
# let x = 2 + 3;;
val x : int = 5
# x * 3;;
- : int = 15
En OCaml, les noms des variables et des fonctions doivent commencer par
une minuscule.
En particulier on peut donner un nom aux fonctions
Les noms commençant par une majuscule sont réservés aux constructeurs.
# let f = (fun x -> x * 2);;
val f : int -> int = <fun>
# f 3;;
- : int = 6
# let IsEven x = x mod 2 =
Characters 4-12:
let IsEven x = x mod 2 =
^^^^^^^^
Error: Unbound constructor
# let isEven x = x mod 2 =
val isEven : int -> bool =
Pour définir une fonction OCaml fournit aussi une syntaxe simplifié
# let g f x = f (f (x * 2));;
val g : (int -> int) -> int -> int = <fun>
# let sq x = x * x;;
val sq : int -> int = <fun>
# let h = g sq;;
val h : int -> int = <fun>
# h 1;;
- : int = 16
let f x y = e
est équivalent à
S. Guerrini (LIPN - Paris 13)
0;;
IsEven
0;;
<fun>
Dans une déclaration, un nom x déclaré dans un let est connu dans toutes les
expressions qui suivent la déclaration, mais pas dans l’expression à la droite
de let x =.
let f = fun x y -> e
Programmation Fonctionnelle
Premiers pas en OCaml
0;;
février 2013
21 / 131
S. Guerrini (LIPN - Paris 13)
Les noms
Programmation Fonctionnelle
Premiers pas en OCaml
Porté du let
février 2013
22 / 131
Les couples
Les produits cartésiens
Les valeurs des produits cartésiens binaires sont les couples
# (3, 4);;
- : int * int = (3, 4)
Dans une déclaration, un nom x déclaré dans un let est connu dans toutes les
expressions qui suivent la déclaration mais pas dans l’expression à la droite de
let x =
Ce n’est pas nécessaire que les objets de la couple aient le même type
# let p = (true, "alpha");;
val p : bool * string = (true, "alpha")
Les projections
# let z = z + 3;;
Characters 8-9:
let z = z + 3;;
^
Error: Unbound value z
# let z = 2;;
val z : int = 2
# let z = z + 3;;
val z : int = 5
#
#
#
-
fst p;;
: bool =
snd p;;
: string
p = (fst
: bool =
true
= "alpha"
p, snd p);;
true
Les produits cartésiens n-aires
Pour définir des fonctions récursives il faut noter qu’on est en train de donner
une déclaration récursive en utilisant le construit let rec.
# (true, 3, "alpha", 4);;
- : bool * int * string * int = (true, 3, "alpha", 4)
I
I
I
les projections fst et snd sont prédéfinies seulement pour le cas binaire n = 2
il n’y a pas des projections prédéfinies pour n 6= 2
mais, on peut définir les projections pour toutes les n-uplets
# let p_1_3 (x, y, z) = x;;
val p_1_3 : ’a * ’b * ’c -> ’a = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
23 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
24 / 131
Premiers pas en OCaml
Les couples
Les fonctions
Fonctions n-aires
Fonction n-aire ou sur n-uplets
Une fonction binaire
# let f x y = x + y;;
val f : int -> int -> int = <fun>
en e↵et, c’est une fonction unaire qui renvoie une fonction unaire
On peut définir une fonction binaire en utilisant un couple
Les fonctions
# let g (x,y) = x + y;;
val g : int * int -> int = <fun>
en e↵et, cette fonction est une fonction unaire sur une couple
La définition précédente est équivalente à
# let g p = fst p + snd p;;
val g : int * int -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les fonctions
février 2013
25 / 131
S. Guerrini (LIPN - Paris 13)
Fonctions n-aires
Programmation Fonctionnelle
Les fonctions
Exemples
février 2013
26 / 131
La récursion
Fonctions récursives
On peut toujours transformer une fonction binaire en une fonction qui prend
ses deux arguments comme éléments d’une couple :
# let curry f (x, y) = f x y;;
val curry : (’a -> ’b -> ’c) -> ’a * ’b -> ’c = <fun>
# let f1 x y = x + y;;
val f1 : int -> int -> int = <fun>
# f1 3 4;;
- : int = 7
Une définition de fonction peut être récursive, mais il faut ajouter le mot clé
rec à let
# let rec exp x =
if (x = 0) then 1
else if (x mod 2 = 1) then (exp (x/2)) * (exp (x/2)) * 2
else (exp (x/2)) * (exp (x/2));;
val exp : int -> int = <fun>
# let f2 = curry f1;;
val f2 : int * int -> int = <fun>
# f2 (3,4);;
- : int = 7
La définition précédente est très inefficace car elle évalue deux fois la même
expression récursive dans chaque branche du cas x > 0.
Pour éviter ce type de problèmes on peut définir des noms locaux
# f3 3 4;;
- : int = 7
# let rec exp x =
if (x = 0) then 1
else let h = exp (x/2) in
if (x mod 2 = 1) then h * h * 2
else h * h;;
On peux aussi bien définir la fonction inverse de curry
# let uncurry f x y = f (x,y);;
val uncurry : (’a * ’b -> ’c) -> ’a -> ’b -> ’c = <fun>
# let f3 = uncurry f2;;
val f3 : int -> int -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
27 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
28 / 131
Les fonctions
La récursion
Les fonctions
Fibonacci
Fonctions itératives
La fonction de Fibonacci
# let rec fib n = if n < 2 then 1 else (fib (n-1) + fib (n-2));;
val fib : int -> int = <fun>
La précédente fonction n’est pas trop efficace : le problème est qu’il y a une
double appel de la fonction récursive.
Essayez de calculer fib 40;;
On peut pourtant coder de façon “récursive” la version “itérative” de
Fibonacci qui calcul au meme temps fib n;; et fib (n-1);;
On n’a pas encore de commandes pour l’itération (while ou repeat).
Mais la possibilité de définir des fonctions d’ordre supérieure (fonctions qui
prennent comme arguments des fonctions et revoient des fonctions comme
résultat) nous permet de définir une fonctionnelle iter qui prend
1
2
3
4
let fibonacci n =
let rec fib n =
if n = 0
then (1,0)
else let p = fib (n-1)
in let x = fst p
and y = snd p
in (x+y, x)
in fst (fib n);;
let rec fib n =
if n = 0
then (1,0)
else let p = fib (n-1)
in let x = fst p
and y = snd p
in (x+y, x);;
un entier n,
une valeur f0 de type ↵
(par exemple : un entier),
une fonction f1 : ↵ ! ↵
(par exemple : une fonction sur les entiers qui double son argument)
et calcul f1n (f0 )
(qu’est-ce qu’on obtient avec les exemples précédentes ?)
let rec iter n f0 f1 =
if n = 0
then f0
else f1 (iter (n-1) f0 f1);;
Programmation Fonctionnelle
Les fonctions
février 2013
let dup x = 2 * x;;
let exp2 n =
fst (iter n 1 dup);;
On peut aussi bien utiliser iter pour définir fibonacci :
let fibstep (x,y) = (x+y, x);;
S. Guerrini (LIPN - Paris 13)
La récursion
29 / 131
S. Guerrini (LIPN - Paris 13)
Les déclarations
Programmation Fonctionnelle
Les fonctions
Déclarations globales et déclarations locales
let fibonacci n =
fst (iter n (1,0) fibstep);;
février 2013
30 / 131
Les déclarations
Déclarations et expressions
Une déclaration globale associe un nom à une valeur
# let x = 3 * 4;;
val x : int = 12
En réponse à une déclaration globale, la boucle affiche l’e↵et que la
déclaration a eu sur l’environnement globale (en ajoute au type et à la valeur
de l’expression).
Dans l’exemple, val x indique que
1
2
le nom x a été ajouté à l’environnement ;
que le nom x est associé à une valeur.
# 4 * let x = 2 in x + 4;;
- : int = 24
Une déclaration globale n’est pas une expression
# 4 * let x = 2;;
Characters 14-16:
4 * let x = 2;;
^^
Error: Syntax error
Dans une déclaration locale
let x = 3 in x + 2;;
le nom déclaré n’est connu que dans l’expression qui suive le in.
Une déclaration locale peut redéfinir localement un nom global
# let x
val x :
# let x
- : int
# x;;
- : int
Une déclaration locale est une expression et on peut l’utiliser comme une
autre expression quelconque du même type
Une déclaration globale peut être utiliser seulement dans des contextes
particuliers, par exemple, comme commande pour la boucle.
= 2;;
int = 2
= 3 in x;;
= 3
= 2
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
31 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
32 / 131
Les fonctions
Les déclarations
Les fonctions
Noms OCaml vs. variables C
Déclarations simultanées et recursion mutuelle
Déclarations simultanées
Dans une déclaration globale ou locale on peut définir plusieurs variable au
même temps et en parallèle dans une déclaration simultanée.
Les noms OCaml et les variables C sont des objets de natures complètement
di↵érentes.
I
I
I
I
En OCaml, une variable est un nom pour une valeur, alors qu’en C une
variable est un nom pour une case mémoire.
En OCaml, il est donc hors de question de pouvoir changer la valeur d’une
variable.
La déclaration globale (ou locale, voir plus loin) d’OCaml, n’a rien à voir avec
l’a↵ectation du C.
Pour obtenir des variables OCaml avec un comportement similaire aux
variables C il faut utiliser
F
F
des données d’un type particulier, les enregistrements à champs modifiables,
et les références (ou pointers).
# let x
val x :
val y :
# let x
- : int
= 3 and y = 4;;
int = 3
int = 4
= y and y = x in (x,y);;
* int = (4, 3)
Dans une déclaration simultanée les expressions sont évaluées avant d’associer
les noms de la déclaration aux valeurs des expressions correspondantes.
Une déclaration simultanée n’est pas équivalent à une chaı̂ne de déclaration
#
#
-
let x
: int
let y
: int
=
*
=
*
y in let y = x in (x,y);;
int = (4, 4)
x in let x = y in (x,y);;
int = (3, 3)
Dans une chaı̂ne de déclarations l’ordre est relevant, tandis que dans une
déclaration simultanée l’ordre des déclarations n’a aucun influence sur le
résultat
# let y = x and x = y in (x,y);;
- : int * int = (4, 3)
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les fonctions
février 2013
33 / 131
S. Guerrini (LIPN - Paris 13)
Déclarations simultanées et recursion mutuelle
Programmation Fonctionnelle
Les fonctions
février 2013
34 / 131
février 2013
36 / 131
Déclarations simultanées et recursion mutuelle
La récursion mutuelle
Les déclarations récursives simultanées permettent de définir des fonctions
mutuellement récursives
Listes polymorphes
# let rec isOdd x = if (x = 0) then false else isEven (x-1)
and isEven x = if (x = 0) then true else isOdd (x-1);;
val isOdd : int -> bool = <fun>
val isEven : int -> bool = <fun>
# let
and g
val
val g
rec f x = if x <= 1 then 1 else g (x+2)
x = f (x-3) + 4;;
f : int -> int = <fun>
: int -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
35 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
Les type list
Les listes en OCaml
Listes polymorphes
Les type list
Construction de listes (1/2)
La liste vide [] est une valeur de type polymorphe
Objective Caml permet de générer des listes d’objets de même type sans que
ce type soit précisé.
On parle pourtant de listes polymorphes.
A partir d’un type quelconque ↵ on peut construire les listes d’éléments de
type ↵.
# [];;
- : ’a list = []
L’opérateur infixé :: ou cons ajoute en élément en tête à une liste
#
#
-
1 :: [];;
: int list = [1]
true :: [];;
: bool list = [true]
On rappel qu’on n’a pas le droit de mélanger des éléments de types di↵érents
dans une liste.
Les listes d’éléments de type ↵ ont type : ’a list
# 1 :: [true];;
Characters 6-10:
1 :: [true];;
^^^^
Error: This expression has type bool but an expression was expected of type
int
On peut construire des listes d’un type quelconque,
mais dans une liste tous les éléments ont le même type.
Le type ’a list est en fait un schéma de type prédéfini.
La plupart des fonctions de manipulation des listes sont définies dans la
bibliothèque List.
On note [e1;...;en] la liste e1 :: (e2 :: ... (en :: []) ... )
# 1 :: 2 :: -2 :: 0 :: 4 :: [];;
- : int list = [1; 2; -2; 0; 4]
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
février 2013
37 / 131
Les type list
38 / 131
Les type list
La déstructuration des listes (l’extraction des éléments de la liste) se fait par
pattern matching.
# [1;2] @ [3;4;5];;
- : int list = [1; 2; 3; 4; 5]
# let f = function
[] -> 0
| x :: l -> x+1;;
val f : int list -> int = <fun>
Attention à la di↵érence entre
et
# [1;2] :: [];;
- : int list list = [[1; 2]]
Les éléments d’une listes peuvent être des listes.
A la place de donner le nom de l’argument de la fonction, on donne des
motifs (patterns) qui correspondent à des valeurs possibles pour l’argument
de la fonction.
# [4;5] :: [[1;2];[];[6];[7;8;5;2]];;
- : int list list = [[4; 5]; [1; 2]; []; [6]; [7; 8; 5; 2]]
Les listes de listes polymorphes ont type : ’a list list
A chaque motif m est associée une expression e
# [[]];;
- : ’a list list = [[]]
m -> e
Si le motif m correspond à la valeur de la variable de la fonction alors le
résultat est la valeur de l’expression e
Par exemple [] -> 0 indique que si l’argument de f est la liste vide, alors le
résultat est 0.
et aussi bien
[[[]]];;
: ’a list list list = [[[]]]
[[[2;3]; [1]];[[]]; [[3]]];; [[[2;3]; [1]];[[]]; [[3]]];;
: int list list list = [[[2; 3]; [1]]; [[]]; [[3]]]
S. Guerrini (LIPN - Paris 13)
février 2013
Déstructuration des listes (1/3)
L’opérateur infixé @ (opérateur de concaténation) concatène deux listes
#
#
-
Programmation Fonctionnelle
Les listes en OCaml
Construction de listes (2/2)
# [1;2] @ [];;
- : int list = [1; 2]
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
# f [];;
- : int = 0
février 2013
39 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
40 / 131
Les listes en OCaml
Les type list
Les listes en OCaml
Déstructuration des listes (2/3)
Déstructuration des listes (3/3)
Les pairs motif->expression (p -> e) sont séparées par une barre verticale
p1 -> e1 | p2 -> e2.
Un motif peut contenir des variables qui, si le motif correspond à la valeur de
l’argument de la fonction, seront associés à une partie de la valeur de
l’argument.
Par exemple, le motif de
x :: l -> x+1
I
Pour prendre les éléments d’une liste on peut également utiliser le fonctions
hd (head) e tl (tail) de la librairie List.
# List.hd [1;3;5];;
- : int = 1
# List.tl [1;3;5];;
- : int list = [3; 5]
Pourtant
correspond à une liste non vide.
I
Les type list
La valeur de tête de la liste est associée à la variable x.
La queue de la liste (c’est-à-dire ce qui reste de la liste sans la tête) est
associée à la variable l.
# (function l -> List.hd l :: List.tl l) [1;3;5];;
- : int list = [1; 3; 5]
La fonction f qu’on viens de voir on pourrait l’écrire
Les valeurs associés aux variables dans un motif sont utilisées pour le calcul
de l’expression associée au motif.
Pourtant, le motif précédent dit que le résultat de la fonction sur une liste
non vide est la valeur de la tête plus 1.
# let f l =
if (l == [])
then 0
else List.hd l + 1;;
val f : int list -> int = <fun>
# f [3;2;7];;
- : int = 4
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
février 2013
41 / 131
S. Guerrini (LIPN - Paris 13)
Les type list
Programmation Fonctionnelle
Les listes en OCaml
Motifs
février 2013
42 / 131
février 2013
44 / 131
Fonctions récursive sur les listes
Fonctions récursives sur les listes
Les motifs peuvent être beaucoup plus compliqués des cas qu’on a vu.
Par exemple,
I
I
pour définir une fonction qui prend une liste de listes d’entiers et ajoute 1 au
premier éléments de la tête de l’argument (et si cet élément n’existe pas alors
la fonction renvoie une constante)
il faut que le motif analyse la tête d’une liste non vide.
# let f =
[] ->
| [] ::
| (x ::
val
#
#
-
f
:
f
:
function
0
l -> 1
l1) :: l2 -> x+1;;
f : int list list -> int = <fun>
La somme des éléments d’une liste d’entiers.
# let rec somme = function
[] -> 0
| x :: l -> x + somme l;;
val somme : int list -> int = <fun>
# somme [3;2;7];;
- : int = 12
La conjonction des éléments d’une liste de booléens.
# let rec conj = function
[] -> true
| x :: l -> x & conj l;;
val conj : bool list -> bool = <fun>
[ []; [1;2] ];;
int = 1
[ [3;2]; []; [1;4] ];;
int = 4
On analysera en détail le pattern matching plus avant.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
43 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
Fonctions récursive sur les listes
Les listes en OCaml
La longueur d’une liste
Fonctionnelle récursives sur les listes
La fonctionnelle map (1/2)
La longueur d’une liste est l’exemple classique de fonction polymorphe sur les
listes : pour la calculer on n’a pas besoin de connaitre le type des éléments de
la liste.
# let rec longueur = function
[] -> 0
| _ :: l -> longueur l + 1;;
val longueur : ’a list -> int = <fun>
longueur [true; false];;
: int = 2
longueur [[2]];;
: int = 1
En utilisant la fonction List.tl, on peut écrire
# let rec longueur l =
if l == []
then 0
else 1 + longueur (List.tl l);;
val longueur : ’a list -> int = <fun>
Considérons l’exemple suivant, qui ajoute 1 à tout élément d’une liste.
# let rec succl = function
[] -> []
| x :: l -> (x+1) :: succl l;;
val succl : int list -> int list = <fun>
# succl [2;1;5];;
- : int list = [3; 2; 6]
#
#
-
Voici maintenant une fonction qui, à une liste d’entiers [n1; . . . ; nk],
associe la liste de booléens [b1; . . . ;bk] telle que bi soit true si et
seulement si ni est pair.
aussi :
On peut enfin utiliser la fonction length de la bibliothèque List. Attention,
même la fonction de la bibliothèque prend un temps proportionnel à la
longueur de la liste.
# let rec pairl = function
[] -> []
| x :: l -> ((x mod 2) = 0) :: pairl l;;
val pairl : int list -> bool list = <fun>
# pairl [1;4;6;3;8];;
- : bool list = [false; true; true; false; true]
# List.length [1;4;5];;
- : int = 3
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
février 2013
45 / 131
Fonctionnelle récursives sur les listes
Programmation Fonctionnelle
Les listes en OCaml
La fonctionnelle map (2/2)
février 2013
Fonctionnelle récursives sur les listes
La librairie List définie aussi deux fonctionnelles d’itération
# List.fold_right;;
- : (’a -> ’b -> ’b) -> ’a list -> ’b -> ’b = <fun>
# List.map;;
- : (’a -> ’b) -> ’a list -> ’b list = <fun>
# List.fold_left;;
- : (’a -> ’b -> ’a) -> ’a -> ’b list -> ’a = <fun>
telle que
List.map f[e1;...;en] = [f e1;...;f en]
Ces deux fonctionnelles permettent l’écriture compacte d’une fonction de
calcul sur les éléments d’une liste comme la somme des éléments.
peut s’écrire très facilement
# let rec map f = function
[] -> []
| x :: l -> f x :: map f l;;
val map : (’a -> ’b) -> ’a list -> ’b list = <fun>
Les e↵ets de fold right et fold left sont les suivants.
fold right f [e1;e2;...;en] a = (f e1 (f e2 ... (f en a)...))
fold left f a [e1;e2;...;en] = (f ... (f (f a e1) e2) ... en)
On peut alors réécrire les expressions vues plus haut en
La fold right correspond à un schéma général de récursion sur les listes.
# List.map (function x->x+1) [3; 2; 6];;
- : int list = [4; 3; 7]
En fait, let g l = fold rigth f l a definie une fonction telle que
g [] = a
g (x :: l) = f x (g l)
# List.map (function x->(x mod 2)=0) [1;4;6;3;8];;
- : bool list = [false; true; true; false; true]
S. Guerrini (LIPN - Paris 13)
46 / 131
Fonctionnelles d’itérations sur les listes (1/4)
La librairie List définie la fonctionnelle
map
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
La fold left correspond plutôt à une iteration sur les élements de la liste de
l’externe vers l’interne.
février 2013
47 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
48 / 131
Les listes en OCaml
Fonctionnelle récursives sur les listes
Les listes en OCaml
Fonctionnelles d’itérations sur les listes (2/4)
Fonctionnelle récursives sur les listes
Fonctionnelles d’itérations sur les listes (3/4)
et fold left permettent d’écrire très rapidement et dans une forme
concise les fonction récursives sur les listes.
fold right
Par exemple, la fonction qui somme les éléments d’une liste peut s’écrire
On peut également redéfinir la fonctionnelle map.
# let somme l = List.fold_right (function x -> function y -> x+y) l 0;;
val somme : int list -> int = <fun>
# let map f ls = List.fold_right (fun x l -> (f x) :: l) ls [];;
val map : (’a -> ’b) -> ’a list -> ’b list = <fun>
De même, la fonction qui concatène deux listes peut s’écrire :
# somme [3;1;9];;
- : int = 13
# let append l1 l2 = List.fold_right (fun x ls -> x :: ls) l1 l2;;
val append : ’a list -> ’a list -> ’a list = <fun>
ou sinon
# let somme l =
let add x y = x+y in
List.fold_right add l 0;;
val somme : int list -> int = <fun>
# append [1;2;3] [4;5;6;7];;
- : int list = [1; 2; 3; 4; 5; 6; 7]
L ’écriture directe de fold right peut se faire de la façon suivante.
# List.append [1;2;3] [4;5;6;7];;
- : int list = [1; 2; 3; 4; 5; 6; 7]
La fonction append est prédéfinie dans la bibliothèque List.
# let rec fold_right f ls a = match ls with
[] -> a
| x :: l -> f x (fold_right f l a);;
val fold_right : (’a -> ’b -> ’b) -> ’a list -> ’b -> ’b = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
février 2013
49 / 131
S. Guerrini (LIPN - Paris 13)
Fonctionnelle récursives sur les listes
Programmation Fonctionnelle
Les listes en OCaml
Fonctionnelles d’itérations sur les listes (4/4)
février 2013
50 / 131
février 2013
52 / 131
Fonctionnelle récursives sur les listes
Quelques autres fonctions du module List
L ’écriture directe de fold left peut se faire de la façon suivante.
# let rec fold_left f a = function
[] -> a
| x :: l -> fold_left f (f a x) l;;
val fold_left : (’a -> ’b -> ’a) -> ’a -> ’b list -> ’a = <fun>
#
#
#
#
#
#
-
En utilisant fold left on peut définir la function qui inverse une liste.
# let rev = List.fold_left (fun l x -> x :: l) [];;
val rev : ’_a list -> ’_a list = <fun>
# rev [1;2;3;4];;
- : int list = [4; 3; 2; 1]
La fonction
List.mem;;
: ’a -> ’a list -> bool = <fun>
List.flatten;;
: ’a list list -> ’a list = <fun>
List.find;;
: (’a -> bool) -> ’a list -> ’a = <fun>
List.split;;
: (’a * ’b) list -> ’a list * ’b list = <fun>
List.combine;;
: ’a list -> ’b list -> (’a * ’b) list = <fun>
List.assoc;;
: ’a -> (’a * ’b) list -> ’b = <fun>
# List.rev;;
- : ’a list -> ’a list = <fun>
est prédéfinie dans la librairie List.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
51 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les listes en OCaml
Fonctionnelle récursives sur les listes
Synthèse de types et polymorphisme
Synthèse de types
Synthèse de types
OCaml est un langage fortement typé
I
I
I
tous les expressions ont un type
le système vérifie que la construction des expressions respect le typage
il n’y a pas des conversions implicites de type
Dans une expression ou dans la déclaration d’une variable il n’y a pas des
indications de type (mais on verra qu’on peut l’ajouter).
Synthèse de types
En OCaml les types sont synthétisés.
L’algorithme de synthèse des types prend une expression et
I
I
soit trouve un type pour l’expression
soit trouve qu’il y a un erreur de typage.
# fun x -> x + (x & true);;
Characters 15-16:
fun x -> x + (x & true);;
^
Error: This expression has type int but an expression was expected of type
bool
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Synthèse de types et polymorphisme
février 2013
53 / 131
S. Guerrini (LIPN - Paris 13)
Synthèse de types
Synthèse de types et polymorphisme
Synthèse du type d’une fonction
54 / 131
Synthèse de types
Dans certains cas, une expression est bien typé mais
I
I
Dans l’expression précédente :
la variable y est utilisée dans deux expressions entiers (y*y et y+1) et donc elle
est de type int ;
la variable x est utilisée comme conditionnel d’un if et donc elle est de type
bool ;
avec ces hypothèses le if-then-else est bien typé parce que l’expression
conditionnel est de type bool et les branches then et else ont le même type ;
le type du valeur renvoyé par le if-then-else (et donc de tout l’expression
if-then-else) est le type des branches then/else et donc c’est int ;
le type de tout l’expression fun est
bool -> int -> int
c’est-à-dire le type d’une fonction qui prend un argument de type bool (la
variable x), un argument de type int (la variable y) et renvoie un résultat de
type int (le résultat de le if-then-else).
Programmation Fonctionnelle
février 2013
Le polymorphisme
# fun x y -> if x then y+1 else y*y;;
- : bool -> int -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
55 / 131
le type de certains paramètres ou de la valeur de retour ne sont pas
complètement précisés ;
ces types ne sont pas nécessaire pour déterminer la valeur de l’expression à
partir de ces paramètres.
# fun x -> x;;
- : ’a -> ’a = <fun>
# let eq x y = x = y;;
val eq : ’a -> ’a -> bool = <fun>
(qu’on lit ↵) est une variable de type et indique que la fonction c’est bien
définie pour un type quelconque ↵.
Les fonctions dont l’un (ou plusieurs) des paramètres ou la valeur de retour
est d’un type qu’il n’est pas nécessaire de préciser sont dites fonctions
polymorphes.
Le type d’une fonction polymorphe peut contenir plus d’une variable de type
’a, ’b, ’c, . . . (qu’on lit ↵, , , . . . )
’a
# let first
val first :
# let third
val third :
= function (x,y) -> x;;
’a * ’b -> ’a = <fun>
(x, y, z) = z;;
’a * ’b * ’c -> ’c = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
56 / 131
Synthèse de types et polymorphisme
Synthèse de types
Synthèse de types et polymorphisme
Utilisation de fonctions polymorphes
Synthèse de types
Des autres exemples de fonctions polymorphes
Une fonction polymorphe peut être utilisé dans tour les contextes compatible
avec le type de la fonction
#
#
-
first (2,3);;
: int = 2
first ("toto",true);;
: string = "toto"
#
#
-
eq 2 3;;
: bool = false
eq true true;;
: bool = true
Si le contexte n’est pas compatible, on a un erreur de typage
# eq true 3;;
Characters 8-9:
eq true 3;;
^
Error: This expression has type int but an expression was expected of type
bool
Remarque
Ce type d’erreur est trouvé par le compilateur avant d’évaluer la fonction.
I
Le typage sert à garantir qu’on n’a pas des erreurs à temps d’exécution liés à une
fonction qui reçoit une valeur de type incorrect.
I
Le polymorphisme de OCaml assure une très grande flexibilité et praticité dans la
définition des fonctions, sans perdre les avantage du typage.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Synthèse de types et polymorphisme
février 2013
57 / 131
# let compose f g x = f (g x);;
val compose : (’a -> ’b) -> (’c -> ’a) -> ’c -> ’b = <fun>
S. Guerrini (LIPN - Paris 13)
février 2013
58 / 131
Synthèse de types
Synthèse de types polymorphes
Si une occurrence d’une variable f a type ⌧1 e une autre a type ⌧2 , le système
cherche une type ⌧ qui est un cas particulier de ⌧1 et de ⌧2 .
# fun f g x -> (f (g x), g (f x));;
- : (’a -> ’a) -> (’a -> ’a) -> ’a -> ’a * ’a = <fun>
la première occurrence de id a le type bool -> bool
la seconde a le type int -> int
Cependant
1
# let g f = ((f true),(f 2));;
Characters 23-24:
let g f = ((f true),(f 2));;
^
Error: This expression has type int but an expression was expected of type
bool
2
Les contraints qui OCaml pose au polymorphisme empêchent que le type de
la fonction f soit polymorphe.
La synthèse des types de OCaml ne permet pas de partir de deux type
particuliers et de trouver un type plus générale :
2
Programmation Fonctionnelle
Synthèse de types et polymorphisme
# let id x = x;;
val id : ’a -> ’a = <fun>
# ((id true),(id 2));;
- : bool * int = (true, 2)
1
= 3
# let g = function f -> f 0;;
val g : (int -> ’a) -> ’a = <fun>
# g (function x->x+1);;
- : int = 1
# g (function x->if x=0 then "toto" else "tata");;
- : string = "toto"
Synthèse de types
Les limites du polymorphisme en OCaml
2
x = (x, x);;
’a -> ’a * ’a = <fun>
x = first (d x);;
’a -> ’a = <fun>
La fonction compose qui compose deux fonctions :
I
1
# let d
val d :
# let g
val g :
# g 3;;
- : int
(f true) force que f soit de type bool -> ’a
(f 2) force que f soit de type int -> ’b
dans f (g x) on a :
dans g (f x) on a :
x: ’a
x: ’d
g: ’a -> ’b
f: ’d -> ’e
f: ’b -> ’c
g: ’e -> ’f
Pour le typage de l’expression complète, il faut trouver des valeurs pour les
variables de type tels que, pour le typage
1
2
3
de x :
de g :
de f :
’a = ’d
’a -> ’b = ’e -> ’f
’b -> ’c = ’d -> ’e
implique
implique
’a = ’e et ’f = ’b
’b = ’d et ’c = ’e
Par conséquent, la solution est
’a = ’d = ’b = ’f = ’e = ’c
Qui force
x: ’a
f: ’a -> ’a
g: ’a -> ’a
on ne peut pas conclure que : “f:’a -> ’a”.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
59 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
60 / 131
Synthèse de types et polymorphisme
Synthèse de types
Synthèse de types et polymorphisme
Unification
Le type le plus général
L’algorithme de synthèse des types est un algorithme d’unification de termes.
Si dans une expression,
I
I
I
I
l’utilisation d’un identificateur x dans une contexte force le type ⌧1 pour x,
tandis que l’utilisation de x dans une autre contexte force le type ⌧2 ,
il faut unifier ⌧1 et ⌧2 ,
alors il faut trouver une substitution pour les variables de type de ⌧1 et ⌧2 (une
fonction ✓ qui replace toutes ou une partie des variables de ⌧1 avec des autres
expressions de type) tel que on obtient le même type ⌧ en appliquant la
substitution à ⌧1 et ⌧2 (c’est-à-dire ✓(⌧1 ) = ⌧ = ✓(⌧2 )).
Exemple :
I
I
I
I
⌧1 = (↵ ! ) ! ⇥ ↵
⌧2 = !
✓ = { 7! ⇥ ↵, 7! ↵ ! ⇥ ↵}
⌧ = ✓(⌧1 ) = ✓(⌧2 ) = (↵ ! ⇥ ↵) !
S. Guerrini (LIPN - Paris 13)
Synthèse de types
Synthèse de types et polymorphisme
I
I
2
février 2013
61 / 131
s’il y a une autre type ⌧ 0 compatible avec l’utilisation de l’identificateur,
alors ⌧ 0 est un cas particulier de ⌧ ,
c’est-à-dire, il y a une substitution tel que ⌧ 0 = (⌧ ).
Pour déterminer le type le plus général l’algorithme d’unification de termes
trouve toujours l’unificateur le plus général ou mgu (most general unifier), s’il
existe.
Une substitution ✓ qui unifie ⌧1 et ⌧2 (et donc ⌧ = ✓(⌧1 ) = ✓(⌧2 )) est le mgu
de ⌧1 et ⌧2 si
1
⇥↵
Programmation Fonctionnelle
En utilisant l’unification, le compilateur calcule le type “le plus général”l
qu’on puisse donner aux identificateurs et à l’expression elle-même qui soit
compatible avec les di↵érentes constructions syntaxiques utilisées.
Le type le plus général ⌧ d’un identificateur a la propriété que
pour chaque substitution ✓0 qui unifie ⌧1 et ⌧2 (et donc ⌧ 0 = ✓0 (⌧1 ) = ✓0 (⌧2 ))
il existe une substitution tel que
✓ = ✓0 (et donc (⌧ ) = ⌧ 0 ).
S. Guerrini (LIPN - Paris 13)
Synthèse de types
Programmation Fonctionnelle
Synthèse de types et polymorphisme
Contraintes de type (1/4)
février 2013
62 / 131
Synthèse de types
Contraintes de type (2/4)
Comme le synthétiseur de types en OCaml engendre le type le plus général, il
peut être utile ou nécessaire de préciser le type t d’une expression expr
La contrainte de type peut être adjointe à n’importe quelle expression ou
sous-expression, y compris les arguments d’une fonction.
# let idint (x : int) = x;;
val idint : int -> int = <fun>
( expr : t )
# let idint x = (x : int);;
val idint : int -> ccint = <fun>
Lorsqu’il rencontre une telle contrainte, le synthétiseur de type en tiendra
compte pour la construction du type de l’expression.
Cette restriction du polymorphisme permet de mieux contrôler le type des
expressions en restreignant le polymorphisme du type déduit par le système.
# let idint = (function (x : int) -> x);;
val idint : int -> int = <fun>
# let idint = (function x -> (x : int));;
val idint : int -> int = <fun>
Il s’agit ici de contraintes et
non d’un typage explicite
remplaçant la synthèse de type d’OCaml.
# let idint = (function x -> x : int -> int);;
val idint : int -> int = <fun>
Dans une contrainte, on peut utiliser n’importe quel type défini, pouvant
contenir des variables de type.
# let idint (x : (’a -> int) -> ’b) = x;;
val idint : ((’a -> int) -> ’b) -> (’a -> int) -> ’b = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
63 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
64 / 131
Synthèse de types et polymorphisme
Synthèse de types
Synthèse de types et polymorphisme
Contraintes de type (3/4)
Contraintes de type (4/4)
L’usage des contraintes de type permet :
de rendre visible le type des paramètres d’une fonction ;
de spécifier le type d’une expression.
# let nil = ([] : int list);;
val nil : int list = []
# let mult (x : int) (y : int) = x * y;;
val mult : int -> int -> int = <fun>
d’interdire l’utilisation d’une fonction en dehors de son cadre ;
# (fun (n : int) ls -> n :: ls) ’a’;;
Characters 30-33:
(fun (n : int) ls -> n :: ls) ’a’;;
^^^
Error: This expression has type char but an expression was expected of type
int
# let cons (n : int) ls = n :: ls;;
val cons : int -> int list -> int list = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Synthèse de types et polymorphisme
Synthèse de types
février 2013
65 / 131
# 3 :: nil;;
- : int list = [3]
# ’a’ :: nil;;
Characters 7-10:
’a’ :: nil;;
^^^
Error: This expression has type int list
but an expression was expected of type char list
S. Guerrini (LIPN - Paris 13)
Synthèse de types
Programmation Fonctionnelle
Synthèse de types et polymorphisme
février 2013
66 / 131
février 2013
68 / 131
Synthèse de types
Définitions de types
En OCaml on peut associer un nome à un type
# type fint = int -> int;;
type fint = int -> int
Gestion de la mémoire
On peut bien définir des types paramétrique ou polymorphes
# type ’a ftype = ’a -> ’a;;
type ’a ftype = ’a -> ’a
# type (’a, ’b) pair = ’a * ’b;;
type (’a, ’b) pair = ’a * ’b
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
67 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Gestion de la mémoire
Mémoire de programme
Gestion de la mémoire
Des zones di↵érents de mémoire
Mémoire de programme
Garbage collection
Mémoire statique
I
I
I
allouée au moment du chargement du programme (e.g., au démarrage de la
boucle interactive).
ne change pas de dimension, mais sont contenu peut se modifier au long de
l’exécution.
contient des données (ou du code) nécessaires tout au long de l’exécution du
programme (e.g., le code et les données de la boucle interactive).
Mémoire dynamique : le tas (en anglais, heap)
I
Des structures (e.g., des listes) sont ajoutées ou supprimées dynamiquement.
Pile d’exécution (en anglais : stack)
I
I
I
I
Comme dans le tas, des données sont ajoutées et supprimées dynamiquement.
Mais, dans la pile, les données sont supprimées en respectant l’ordre inverse
d’allocation (DEPS)
La pile est organisée en cadres de piles (en anglais, stack frames) qui
contiennent les données des appels de fonctions.
La pile contient aussi des valeurs intermédiaire nécessaires seulement pendant
l’évaluation di une expression de la fonction.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Gestion de la mémoire
février 2013
69 / 131
I
C’est la tâche du Garbage Collector (GC, parfois Ramassage de miettes), qui
est lancé quand le système OCaml a besoin de mémoire.
Comme en Java.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
70 / 131
Mémoire de programme
Appel terminal
Une fonction f appèle une fonction g et le résultat de l’évaluation de g est
envoyé tout de suite par f.
les valeurs locales et les paramètres d’un appel de fonction
les informations nécessaires pour revenir au point de l’appel de la fonction
I
Quand cet appel de fonction est terminé, sa mémoire locale est balayée du
sommet de la pile.
Dans l’évaluation d’une fonction récursive on a un cadre de pile pour chaque
appel récursif.
I
Quand une structure (par exemple une liste) n’est plus accessible alors son
espace mémoire peut être réutilisé.
Gestion de la mémoire
A chaque appel de fonction, OCaml alloue un cadre sur la pile.
Chaque cadre de pile stockes (parmi d’autres)
I
Le structures sont alloué au moyen des constructeur du type de données
(e.g., pour les liste le cons ::).
Mémoire de programme
La pile
I
En OCaml on n’a pas des commandes explicites d’allocation ou de-allocation
de la mémoire.
Pourtant, si on fait une récursion trop profonde on risque d’épuiser l’espace
disponible pour la pile.
Mais attention, le risque est réel seulement si ont fait au moins des dizaines ou
centaines de milliers d’appels récursives.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
71 / 131
exemple d’appel terminal :
let f x = if x = 0 then 0 else g x
I
exemple d’appel non-terminal :
let f x = x + g x
Dans le cas d’appel terminal, on n’a plus besoin des valeurs locales de la
fonction f quand on évalue g.
Le cadre de f sur la pile peut être libéré avant d’évaluer g.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
72 / 131
Gestion de la mémoire
Mémoire de programme
Gestion de la mémoire
Récursion terminale
Mémoire de programme
Réécrire une fonction en récursion terminale
Il est parfois possible de réécrire une fonction récursive en une fonction
récursive terminale équivalente.
En anglais, tail recursion.
let rec somme = function
| [] -> 0
| hd :: tl -> hd + somme tl;;
C’est une fonction récursive, avec tous les appels récursifs à des positions
terminales.
let somme ls =
let rec somme_term acc = function
| [] -> acc
| hd :: tl -> somme_term (hd + acc) tl
in somme_term 0 ls;;
Normalement, il faut ajouter des paramètres à la fonction.
let rec fold_left f b = function
| [] -> b
| h::r -> fold_left f (f b h) r;;
I
I
Peut importe la profondeur de récurrence, l’utilisation d’espace reste
constante :
Une fonction récursive terminale correspond à une version itérative de la
fonction.
Les paramètres additionnels sont les données (l’état) modifiées à chaque
itération.
let fibonacci n =
let rec fib n =
if (n = 0) then (1,0)
else let (a,b) = fib (n-1)
in (a+b, a)
in fst (fib n);;
La fonction récursive est exécutée comme une boucle d’itération !
let fibonacci n =
let rec fib (a,b) n =
if (n = 0) then (a,b)
else fib (a+b, a) (n-1)
in fst (fib (1, 0) n);;
La récursion terminale permet d’éviter des dépassements de pile (en anglais,
stack overflow).
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Gestion de la mémoire
février 2013
73 / 131
S. Guerrini (LIPN - Paris 13)
Mémoire de programme
Programmation Fonctionnelle
Gestion de la mémoire
février 2013
74 / 131
Mémoire de programme
Récursion terminale et fonctions fold
La fonction fold left est récursive terminale :
let rec fold_left f b = function
| [] -> b
| h::r -> fold_left f (f b h) r;;
Types enumerés et récurifs
La fonction fold right n’est pas récursive terminale :
let rec fold_right f l b = match l with
| [] -> b
| h::r -> f h (fold_right f r b);;
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
75 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
76 / 131
Gestion de la mémoire
Mémoire de programme
Les types énumérés
Somme de types
Les types énumérés
La définition d’un type se fait à l’aide du mot clé type.
Un constructeur permet de construire les valeurs d’un type et d’accéder aux
composantes de ces valeurs grace au filtrage des motifs.
Types énumérés
# type couleur = Bleu | Vert | Rouge;;
type couleur = Bleu | Vert | Rouge
# let valcoul = function
Bleu -> 1
| Rouge -> 2
| Vert -> 3;;
val valcoul : couleur -> int = <fun>
# valcoul Rouge;;
- : int = 2
Les noms des constructeurs commencent par une majuscule.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les types énumérés
février 2013
77 / 131
S. Guerrini (LIPN - Paris 13)
Somme de types
Programmation Fonctionnelle
Les types énumérés
La somme de types ou types variants
février 2013
78 / 131
Somme de types
Constructeurs et motifs
La somme de deux types correspond à l’union disjointe.
# type int_ou_bool = Int of int | Bool of bool;;
type int_ou_bool = Int of int | Bool of bool
# Int 4;;
- : int_ou_bool = Int 4
# Bool true;;
- : int_ou_bool = Bool true
L’utilisation des expression de ce type se fait par pattern matching .
# let f = function
Bool x -> if x then 0 else 1
| Int x -> if x < 0 then x else x + 2;;
val f : int_ou_bool -> int = <fun>
Les constructuer ne sont pas des fonctions
# Int;;
Characters 0-3:
Int;;
^^^
Error: The constructor Int expects 1 argument(s),
but is applied here to 0 argument(s)
# Int false;;
Characters 4-9:
Int false;;
^^^^^
Error: This expression has type bool but an expression was expected of type
int
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
79 / 131
#
#
#
-
f
:
f
:
f
:
(Int 3);;
int = 5
(Int (-2));;
int = -2
(Bool true);;
int = 0
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
80 / 131
Les types énumérés
Types énumérés paramétrés
Les types énumérés
Types énumérés paramétrés
Exemples
On peut aussi definir des types énumérés paramétrés.
Les constructeur de ces types prennent des élement de types paramétrés.
Une fonction qui échange les types des elements des constructeur.
# let f = function
Left x -> Right x
| Right x -> Left x;;
val f : (’a, ’b) sum -> (’b, ’a) sum = <fun>
# type (’a, ’b) sum = Left of ’a | Right of ’b;;
type (’a, ’b) sum = Left of ’a | Right of ’b
#
#
#
-
Left 3;;
: (int, ’a) sum = Left 3
Right false;;
: (’a, bool) sum = Right false
Right [(1,2)];;
: (’a, (int * int) list) sum = Right [(1, 2)]
#
#
-
Programmation Fonctionnelle
Les types énumérés
# let g = function
Left x -> Right x
| Right x -> Left (x+1);;
val g : (’a, int) sum -> (int, ’a) sum = <fun>
#
#
février 2013
81 / 131
Par rapport au résultat, il n’y a aucun information dans une expression de type
unit
On sait déjà que sont valeur sera ()
Le type unit et la valeur () sont une sorte d’entités vides.
Ils ne sont pas l’équivalent de ce que de void ou NULL dans d’autres langages.
Utilisation principales du type unit :
I
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
82 / 131
Le type unit
Fonctions sans arguments
# () ;;
- : unit = ()
I
(Left "hello");;
(int, string) sum = Right "hello"
(Right 3);;
(int, ’a) sum = Left 4
Les types énumérés
Le type unit est un type enumeré predefini qui contient une unique valeur :
I
g
:
g
:
Le type unit
Le type unit
I
f (Left "hello");;
: (’a, string) sum = Right "hello"
f (Right 3);;
: (int, ’a) sum = Left 3
Une autre fonction qui échange le rôle des constructeurs.
# let left_ou_right = function
Left x -> true
| Right x -> false;;
val left_ou_right : (’a, ’b) sum -> bool = <fun>
# left_ou_right (Left true);;
- : bool = true
# left_ou_right (Left "hello");;
- : bool = true
# left_ou_right (Right "hello");;
- : bool = false
S. Guerrini (LIPN - Paris 13)
Types énumérés paramétrés
C’est le type de l’input d’une fonction qui ne prennent pas d’argument
particulier.
C’est le type de retour d’une fonction n’ayant pas une réelle valeur de retour.
On veut écrire une fonction qui ajoute 1 à un entier lis de l’input.
La fonction read_int: unit -> int prend un entier de l’input standard et le
renvoie comme valeur.
# let f = read_int () + 1;;
# let g () = read_int () + 1;;
3
<---- input
val f : unit -> int = <fun>
val int : 4
I f est une expression et elle est évaluée tout de suite :
on lit la valeur 3 de l’input et on obtient la valeur 4
I les évaluations ultérieurs de f donneront toujours 4
I g est une fonction et son corps est évalué seulement lorsqu’on l’appel
Quand et comment-on évalue la fonction g ?
I
I
I
I
I
Moralement, g est une fonction de 0 arguments.
Formellement, elle est une fonction sur le type unit
La seule valeur d’input possible, c’est pourtant ().
Le corps de la fonction est évalué a tout appel de la fonction.
Tout appel de la fonction g a la forme g ().
# g ();;
3
<---- input
- : int = 4
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
83 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
84 / 131
Les types énumérés
Le type unit
Les types énumérés
Le type unit
Fonctions qui ne renvoie pas une valeur (procédures)
Le type unit est utilisé par et en combinaison avec
les primitives impératives de OCaml.
Types récursifs
Si la fonction g: unit -> int ne dépend pas de l’input ou d’autres valeurs qui
peuvent changer
I
I
la fonction aura toujours la même valeur
ce n’est pas la peine de l’exécuter tout le fois
Le type unit est utilisé comme type du résultat d’une fonction qui
I
I
réalise une action (de type impératif) qui dépend de l’état ou de l’input
mais ne renvoie aucune valeur particulière.
# print_endline "OCaml";;
OCaml
<---- output
- : unit = ()
print_endline: string -> unit
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les types récursifs
février 2013
85 / 131
S. Guerrini (LIPN - Paris 13)
Entiers en notation unaire
Programmation Fonctionnelle
Les types récursifs
Types récursifs : entiers en notation unaire
février 2013
86 / 131
février 2013
88 / 131
Les listes
Types récursifs : listes d’entiers
On utilise les constructeurs pour définir des types récursifs.
Une définition alternative des entiers (notation unaire !).
# type entier = Zero | Succ of entier;;
type entier = Zero | Succ of entier
On peut redéfinir le type liste d’entiers avec
# type liste_entiers = Nil | Cons of entier * liste_entiers;;
type liste_entiers = Nil | Cons of entier * liste_entiers
# let rec somme = function
Nil -> Zero
| Cons (x,l) -> add x (somme l);;
val somme : liste_entiers -> entier = <fun>
Les fonctions successeur et prédécesseur.
# let succ x = Succ x;;
val succ : entier -> entier = <fun>
# let pred = function
Zero -> Zero
| Succ x -> x;;
val pred : entier -> entier = <fun>
Dans cet exemple,
I
I
La somme de deux entiers unaires
Nil est un constructeur d’arité 0
Cons un constructeur d’arité 2.
# let rec add x = function
Zero -> x
| Succ y -> Succ (add x y);;
val add : ’a -> entier -> ’a = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
87 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les types récursifs
Les listes
Les types récursifs
Types récursifs : listes polymorphes
Les listes
Types récursifs : listes hétérogènes
On peut également recréer le type polymorphe des listes en définissant le type
paramétré suivant.
On peut utiliser les types paramétrés pour créer par exemple des listes
pouvant contenir des valeurs de types di↵érents.
# type ’a liste = Nil | Cons of ’a * (’a liste);;
type ’a liste = Nil | Cons of ’a * ’a liste
# let rec liste_fold_right f l b =
match l with
Nil -> b
| Cons (x,reste) -> f x (liste_fold_right f reste b);;
val liste_fold_right : (’a -> ’b -> ’b) -> ’a liste -> ’b -> ’b = <fun>
# type (’a,’b) liste =
Nil
| Acons of ’a * (’a,’b) liste
| Bcons of ’b * (’a,’b) liste;;
type (’a, ’b) liste =
Nil
| Acons of ’a * (’a, ’b) liste
| Bcons of ’b * (’a, ’b) liste
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les types récursifs
février 2013
89 / 131
S. Guerrini (LIPN - Paris 13)
Les listes
Programmation Fonctionnelle
Pattern Martching
février 2013
90 / 131
février 2013
92 / 131
Patterns
Matching
Pattern matching
(Filtrage de motifs)
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Un exemple déjà vu.
# let rec filter l p = match l with
[] -> []
| x :: reste -> let l = filter reste p in
if p x then (x :: l) else l;;
val filter : ’a list -> (’a -> bool) -> ’a list = <fun>
février 2013
91 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Pattern Martching
Patterns
Pattern Martching
Patterns
La construction
Les di↵érentes formes de motifs vues jusqu’ici sont les suivantes.
I
Si e, e1,...,en sont des expressions et si p1,. . . ,pn sont des motifs, alors
match e with p1 ->
soit un élément d’un type de base prédéfini (bool, int, string, ...),
soit un constructeur d’arité 0, comme par exemple le constructeur []).
Si C est un constructeur (le constructeur binaire ; des listes :: entre bien sûr
dans cette catégorie même si, dans ce cas, la notation est infixe) d’arité n1 et
si p1, . . . , pn sont des motifs, alors p = C (p1,...,pn) est un motif ;
Si l1, . . . , lk sont certains des noms de champs d’un type enregistrement et
si p1, . . . , pk sont des motifs, alors p = l1 = p1;...;lk = pk est un motif.
Programmation Fonctionnelle
Pattern Martching
février 2013
e1 | ··· | pn -> en
est une expression, qui est equivalente à
Toute variable est un motif.
S. Guerrini (LIPN - Paris 13)
match
La construction match a la signification suivante.
Toute constante est un motif.
Etant entendu qu’une constante est
I
La construction match
93 / 131
(function p1 -> e1 | ··· | pn -> en) e
Inversement,
function p1 -> e1 |···| pn -> en
peut s’ecrire
function x = (match x with p1 -> e1 |···| pn -> en)
S. Guerrini (LIPN - Paris 13)
La construction match
Programmation Fonctionnelle
Pattern Martching
Pattern matching non exhaustif
février 2013
94 / 131
février 2013
96 / 131
La construction match
Le pattern universel
Les patterns peuvent ne pas couvrir tous les cas possibles :
# let f l = match l with [] -> 0;;
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
_::_ val f : ’a list -> int = <fun>
Il existe un motif qui filtre n’importe quelle valeur.
La valeur filtrée est perdue après le filtrage.
Néanmoins, la fonction est definie sur les cas filtrés par un pattern :
# f [];;
- : int = 0
Mais, elle n’est pas définie sur les cas qui ne correspondent à aucun pattern :
# let rec meme_lg l1 l2 =
match (l1,l2) with
([],[]) -> true
| (_::reste1, _::reste2) -> meme_lg reste1 reste2
| (_, _) -> false
val meme_lg : ’a list -> ’b list -> bool = <fun>
# f [1];;
Uncaught exception: Match_failure ("", 10, 35).
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
95 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Pattern Martching
La construction match
Pattern Martching
La construction match
Linéarité
Les motifs doivent être linéaires, c’est-à-dire qu’une même variable ne peut
apparaı̂tre qu’au plus une fois dans un motif.
# let eg = function
(x,x) -> true
| _ -> false;;
This variable is bound several times in this matching
Les arbres
La construction as permet de nommer un sous-motif d’un motif.
# let f = function [] -> []
| x::[] -> []
| x::((y::m) as l) -> m@l;; val f :
S. Guerrini (LIPN - Paris 13)
a list ->
a list = <fun>
Programmation Fonctionnelle
Les arbres
février 2013
97 / 131
S. Guerrini (LIPN - Paris 13)
Arbres binaires
Programmation Fonctionnelle
Les arbres
Arbres binaires
98 / 131
Arbres binaires
Fonctions récursives sur des arbres binaires
Arbres binaires avec des étiquettes entiers sur les noeud.
Une fonction qui compte les noeuds d’un arbre :
# type arbre = Vide | Noeud of int * arbre * arbre;;
# let rec taille = function
Vide -> 0
| Noeud (n, t1, t2) -> 1 + taille t1 + taille t2;;
val taille : ’a arbre -> int = <fun>
et avec des étiquettes de type quelconque (mais toutes du même type)
# type ’a arbre = Vide | Noeud of ’a * (’a arbre) * (’a arbre);;
Un exemple d’arbre binaire :
Une fonction qui somme les étiquettes d’un arbre d’entiers :
let ex = Noeud (3,
Noeud (2,
Vide,
Noeud (5, Vide, Vide)),
Noeud (1,
Vide,
Noeud (2,
Vide,
Vide)));;
val ex : int arbre =
Noeud (3, Noeud (2, Vide, Noeud (5, Vide, Vide)),
Noeud (1, Vide, Noeud (2, Vide, Vide)))
S. Guerrini (LIPN - Paris 13)
février 2013
Programmation Fonctionnelle
# let rec somme = function
Vide -> 0
| Noeud (n, t1, t2) -> n + somme t1 + somme t2;;
val somme : int arbre -> int = <fun>
Une fonction qui élimine les sous-arbres qui vérifient une condition donnée :
let rec tranche p = function
| Vide -> Vide
| Noeud (x, t1, t2) as t ->
if (p t) then Vide
else Noeud (x, tranche p t1, tranche p t2);;
février 2013
99 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
100 / 131
Les arbres
Arbres binaires
Les arbres
Fonctionnelles d’itérations
Parcours des arbres binaires
On peut définir des fonctionelles d’itérations semblables à celles (prédéfinies)
sur les listes (fold).
La fonction suivante est l’équivalent d’un fold right
# let fold_arbre f a =
let rec fold = function
Vide -> a
| Noeud (x, t1, t2) -> f x (fold t1) (fold t2)
in fold;;
val fold_arbre : (’a -> ’b -> ’b -> ’b) -> ’b -> ’a arbre -> ’b = <fun>
On peut écrire une version de infix de meilleure complexité à l’aide d’une
variable appelée accumulateur.
let infix t =
let rec infix_accu t accu = match t with
| Vide -> accu
| Noeud (x, t1, t2) -> infix_accu t1 (x :: infix_accu t2 accu)
in infix_accu t [];;
la fonction qui somme les étiquettes d’un arbre
# let somme t = fold_arbre (fun x y1 y2 -> x + y1 + y2) 0 t;;
val somme : int arbre -> int = <fun>
I
La fonction infix suivante donne la projection verticale d’un arbre ou l’ordre
interne ou encore infixé.
# let rec infix = function
| Vide -> []
| Noeud (x, t1, t2) -> (infix t1) @ (x :: infix t2);;
val infix : ’a arbre -> ’a list = <fun>
A l’aide de fold arbre on peur réécrire
I
Arbres binaires
et la fonction qui cherche la plus grande étiquette
# let greatest t =
fold_arbre (fun x y1 y2 -> max x (max y1 y2)) min_int t;;
val greatest : int arbre -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les arbres
février 2013
101 / 131
S. Guerrini (LIPN - Paris 13)
Arbres binaires
Programmation Fonctionnelle
Les arbres
Variantes
février 2013
102 / 131
Expressions arithmétiques
Expressions arithmétiques
Des arbres avec des étiquettes sur les feuilles seulement :
# type ’a arbre = Feuille of ’a | Noeud of ’a arbre * ’a arbre;;
On peut définir un type pour les opérateurs arithmétiques :
Un exemple d’arbre
# type opArith = Somme | Mult;;
# Noeud (Noeud (Feuille 2, Noeud (Feuille 3, Feuille 1)),
Noeud (Feuille 0, Noeud (Feuille (-1), Feuille 4)));;
- : int arbre =
...
Une expression sera un arbre avec des entiers aux feuilles et des opérateurs
arithmétiques aux autres noeuds :
Des arbres avec des étiquettes de types di↵érents sur les feuilles et sur les
autres noeuds :
# type (’a, ’b) arbre =
Feuille of ’a
| Noeud of ’b * (’a, ’b) arbre * (’a, ’b) arbre;;
Un exemple d’arbre
# Noeud (’+’, Noeud (’*’, Feuille 2, Noeud (’+’, Feuille 3, Feuille 1)),
Noeud (’*’, Feuille 0, Noeud (’+’, Feuille (-1), Feuille 4)));;
- : (int, char) arbre =
...
let aex = Noeud (Somme,
Noeud (Mult,
Feuille 2,
Noeud (Somme, Feuille 3, Feuille 1)),
Noeud (Mult,
Noeud (Somme, Feuille (-1), Feuille 4),
Feuille 0));;
val aex : (int, opArith) arbre =
...
Donc le type des expressions sera défini par :
# type expr = (int, opArith) arbre;;
qui représente une expression arithmétique.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
103 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
104 / 131
Les arbres
Expressions arithmétiques
Les arbres
Evaluation des expressions arithmétiques
Expressions arithmétiques
Arbres syntaxiques
Une autre représentation des expressions : les opérateurs deviennent des
constructeurs de l’arbres.
type expr =
Val of int
| Sum of expr * expr
| Prod of expr * expr;;
Pour calculer la valeur d’une expression, on écrit une fonction récursive :
let rec eval_expr = function
Feuille n -> n
| Noeud (Somme, t1, t2) -> eval_expr t1 + eval_expr t2
| Noeud (Mult, t1, t2) -> eval_expr t1 * eval_expr t2;;
Un exemple d’expression :
# let ex = Sum (Prod (Val
Sum
Prod (Sum
Val
val ex : expr =
...
Une autre écriture de la fonction d’évaluation :
# let rec eval_expr = function
Feuille n -> n
| Noeud (op, t1, t2) ->
let (v1, v2) = (eval_expr t1, eval_expr t2)
in match op with
Somme -> v1 + v2
| Mult -> v1 * v2;;
val eval_expr : (int, opArith) arbre -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les arbres
L’avantage de ce type de représentation est qu’on peut ajouter facilement des
opérateur non-binaires, par exemple, l’opérateur unarie Neg n qui donne
l’opposé n de son argument et l’opérateur Exp n qui donne 2n .
type expr =
Val of int
| Sum of expr * expr
| Prod of expr * expr
| Neg of expr
| Exp of expr;;
février 2013
105 / 131
Programmation Fonctionnelle
Les arbres
février 2013
106 / 131
Expressions arithmétiques avec des variables
La valeur des variables : l’environnement
Pour évaluer une expression qui contient des variables, on a besoin de
connaitre les valeurs des variables.
On ajoute la possibilité que les feuilles soient des variables :
On a besoin de ce qu’on appelle l’environnement.
type expr =
Val of int
| Var of char
| Sum of expr * expr
| Prod of expr * expr;;
L’environnement est une fonction qui associe une valeur aux variables.
Par exemple :
let env = function
’x’ -> 2
| ’y’ -> 3
| ’z’ -> -1
| _ -> 0;;
Un exemple d’expression avec des variables :
S. Guerrini (LIPN - Paris 13)
S. Guerrini (LIPN - Paris 13)
Expressions arithmétiques avec des variables
Expressions arithmétiques avec des variables
# let ex2 = Sum (Prod (Var
Sum
Prod (Sum
Var
val ex2 : expr =
...
2,
(Val 3, Val 1)),
(Val (-1), Val 4),
0));;
’x’,
(Val 3, Var ’z’)),
(Var ’y’, Val 4),
’x’));;
Comme ça, on donne une valeur 0 à toutes variables qu’on n’utilises pas.
Mais, on peut aussi laisser ces valeurs indéfinies :
let env
’x’
| ’y’
| ’z’
Programmation Fonctionnelle
février 2013
107 / 131
= function
-> 2
-> 3
-> -1;;
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
108 / 131
Les arbres
Expressions arithmétiques avec des variables
Les arbres
La fonction d’évaluation
Expressions arithmétiques avec des variables
Variables non-définies
La fonction d’évaluation des expressions dans un environnement :
let rec eval_expr env = function
Val n -> n
| Var x -> env x
| Sum (t1, t2) -> eval_expr env t1 + eval_expr env t2
| Prod (t1, t2) -> eval_expr env t1 * eval_expr env t2;;
Une version qui lève une exception si on évalue une variable non-définies :
Une autre version qui élimine le passage du paramètre env dans les appels
récursif :
let eval_expr env =
let rec eval = function
Val n -> n
| Var x -> env x
| Sum (t1, t2) -> eval t1 + eval t2
| Prod (t1, t2) -> eval t1 * eval t2
in eval;;
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les arbres
février 2013
109 / 131
# let eval_expr =
let env = function
x -> failwith ("Undef var ’" ^ Char.escaped x ^ "")
in let rec eval = function
Val n -> n
| Var x -> env x
| Sum (t1, t2) -> eval t1 + eval t2
| Prod (t1, t2) -> eval t1 * eval t2
in eval;;
val eval_expr : expr -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Expressions arithmétiques avec des variables
Programmation Fonctionnelle
Les arbres
A↵ectation d’une valeur à une variable locale :
le constructeur Let
février 2013
110 / 131
Expressions arithmétiques avec des variables
A↵ectation d’une valeur à une variable locale :
évaluation du Let
Au moment de l’évaluation du Let on ajoute une nouvelle variable à
l’environnement avec la valeur donnée :
On ajoute une construction
Let (x,n,e)
qui associe un entier n à une variable x dans l’évaluation di une expression e.
On étend la définition du type des expression avec un constructeur Let :
type expr =
Val of int
| Var of char
| Let of char * int * expr
| Sum of expr * expr
| Prod of expr * expr;;
La portée de l’a↵ection de la valeur n à la variable x est limitée à l’expression
e (comme dans OCaml).
# let rec eval_expr env =
let rec eval = function
Val n -> n
| Var x -> env x
| Let (c, n, t) ->
let newenv x = if (x = c) then n else env x
in eval_expr newenv t
| Sum (t1, t2) -> eval t1 + eval t2
| Prod (t1, t2) -> eval t1 * eval t2
in eval;;
val eval_expr : (char -> int) -> expr -> int = <fun>
L’environnement newenv est utilisé seulement dans l’évaluation de
l’expression t du Let.
Les autres cas sont tous évalués dans l’environnement env.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
111 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
112 / 131
Les arbres
Expressions arithmétiques avec des variables
Les arbres
A↵ectation d’une expression à une variable locale
Arbres généraux
Arbres généraux
La valeur de l’a↵ectation devient une valeur :
Arbres avec un nombre de branche variable
type expr =
Val of int
| Var of char
| Let of char * expr * expr
| Sum of expr * expr
| Prod of expr * expr;;
# type ’a arbre = Noeud of ’a * ’a arbre list;;
Un exemple d’arbre :
Au moment de l’évaluation du Let il faut d’abord évaluer l’expression qui
donne la valeur de la variable dans l’environnement env.
# let rec eval_expr env =
let rec eval = function
Val n -> n
| Var x -> env x
| Let (c, v, t) ->
let newenv x = if (x = c) then (eval v) else (env x)
in eval_expr newenv t
| Sum (t1, t2) -> eval t1 + eval t2
| Prod (t1, t2) -> eval t1 * eval t2
in eval;;
val eval_expr : (char -> int) -> expr -> int = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Les arbres
let ex = Noeud (3,
[ Noeud (2,
[ Noeud (5, []);
Noeud (2,
[ Noeud
Noeud
Noeud (4, []);
Noeud (1,
[ Noeud
Noeud (1,
[ Noeud (2, []);
Noeud (1,
[ Noeud
(3, []);
(7, []) ]);
(3, []) ]) ]),
(3,
[ Noeud (4, []);
Noeud (1, []);
Noeud (7, []) ]) ]) ]) ]);;
val ex : int arbre =
...
février 2013
113 / 131
S. Guerrini (LIPN - Paris 13)
Arbres généraux
Programmation Fonctionnelle
Les arbres
février 2013
114 / 131
février 2013
116 / 131
Arbres généraux
Fonctions récursives sur des arbres
Une fonction qui compte les noeuds d’un arbre :
let rec compte (Noeud (x, ls)) =
let rec compte_ls = function
| [] -> 0
| t :: r -> compte t + compte_ls r
in 1 + compte_ls ls;;
Input/Output
Une fonction qui élimine les sous-arbres qui vérifient une condition donnée :
# let
let
and
|
|
tranche p =
rec tranchep (Noeud (x, ls)) = Noeud (x, tranchep_ls ls)
tranchep_ls = function
[] -> []
t :: r ->
if (p t)
then tranchep_ls r
else tranchep t :: tranchep_ls r
in tranchep;;
val tranche : (’a arbre -> bool) -> ’a arbre -> ’a arbre = <fun>
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
115 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Output
Ecrire dans la sortie standard
Output
Sortie de valeurs de base
La fonction printf
OCaml prévoit des fonctions de sortie vers stdout pour tous les types de
base.
#
#
#
#
-
Le module Printf définie une fonction printf qui
1
print_int;;
: int -> unit = <fun>
print_float;;
: float -> unit = <fun>
print_string;;
: string -> unit = <fun>
print_char;;
: char -> unit = <fun>
2
I
I
I
%i dénote un entier
%s une chaı̂ne de caractères
...
Un exemple :
# print_newline;;
- : unit -> unit = <fun>
L’interpréteur sait afficher des valeurs de type autre que les types de bases
(e.g., listes et nuplet).
Par contre, si on veut sortir une telle valeur vers stdout alors il faut écrire
une fonction pour le faire.
Programmation Fonctionnelle
Output
février 2013
117 / 131
# Printf.printf "entier: %i\nstring: %s\n" 4 "toto";;
entier: 4
string: toto
- : unit = ()
Similaire à printf dans C, C++, Java, ...
Le typage de printf et des autres fonctions d’output (input) formaté est
particulier :
I
le nombre et les types des arguments dépendent du premier argument : la
chaı̂ne du format d’output
S. Guerrini (LIPN - Paris 13)
Ecrire dans un canal d’output
février 2013
118 / 131
Ecrire dans un canal d’output
Fonctions d’écriture
Tout processus UNIX a trois canaux de communication (voir un cours de
Systèmes) :
Il y a des variantes des fonction d’écriture vues précédemment pour écrire
I
stdin : entrée normale du processus, normalement associée au clavier.
Peut aussi être une redirection d’un tuyau ou d’un fichier.
I
stdout : sortie normale du processus, normalement associée à l’écran.
Peut aussi être redirigée vers un tuyau ou un fichier.
stderr : sortie pour les messages d’erreur.
Normalement confondue avec stdout et associée à l’écran, mais
peut aussi être redirigée.
Programmation Fonctionnelle
Programmation Fonctionnelle
Output
Canaux de communication
S. Guerrini (LIPN - Paris 13)
prend en premier argument une chaı̂ne qui décrit le format,
puis tant d’arguments que demandé par le format.
Dans le format,
La sortie vers stdout n’est pas e↵ectuée tout de suite : il y a un tampon.
La fonction print newline force la sortie du contenu du tampon de sortie.
S. Guerrini (LIPN - Paris 13)
Ecrire dans la sortie standard
février 2013
119 / 131
dans un canal de sortie quelconque
dans une chaı̂ne de caractères.
Il y a aussi des fonctions analogues pour la sortie vers stderr.
Attention : distinguer toujours stdout et stderr : un utilisateur peut avoir
besoin de séparer la sortie normale des messages d’erreur.
I
Fonctions : prerr int, etc. (voir le manuel)
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
120 / 131
Output
Ecrire dans un fichier
Output
Ouverture et fermeture d’un fichier d’output
Fonctions d’écriture
Fonction open out pour ouvrir un fichier
I
I
I
I
Type : string -> out channel.
Si le fichier n’existe pas il est créé.
Le résultat est un canal d’output : pour écrire dans le fichier il faudra écrire
dans cet canal.
open out peut lever une exception Sys error,
F
par exemple, quand on n’a pas les droits nécessaires pour
créer ou ouvrir le fichier.
Programmation Fonctionnelle
Input
février 2013
121 / 131
Il n’y a pas de output int
En revanche, il y a une variante de printf, du nom fprintf, pour écrire
dans un canal.
La sortie vers un canal est tamponnée.
Fonctions flush pour vider le tampon d’en canal
et flush all pour vider tous les tampons de sortie.
S. Guerrini (LIPN - Paris 13)
Canal d’entrée standard
février 2013
122 / 131
Lire dans un fichier
Lire dans un fichier
La fonction read line attend sur stdin une ligne terminée par
retour-chariot, et envoie comme résultat le contenu de cette ligne (sous forme
d’un string) mais sans le retour-chariot.
Il y a également read int et read float.
Le module Scanf permet de lire des lignes dans un format précis (analogue à
Printf).
Problème dans le cas du mode emacs : la boucle attend toujours ;; pour
terminer une entrée.
Programmation Fonctionnelle
Programmation Fonctionnelle
Input
Entrée par le canal d’input standard
S. Guerrini (LIPN - Paris 13)
Le premier argument est le canal.
I
Type : out channel -> unit
S. Guerrini (LIPN - Paris 13)
Fonctions output string, output char pour écrire dans un canal de sortie.
I
Fonction close out pour fermer un fichier
I
Ecrire dans un fichier
février 2013
123 / 131
Fonction open in pour ouvrir un fichier en lecture.
I
open in lève l’exception Sys error si le fichier ne peut pas être ouvert.
F
Par exemple, parce qu’il n’existe pas.
Fonction close in pour fermer le canal.
Fonction input line pour lire une ligne complète.
I
input line lève l’exception End of file quand on est à la fin du fichier.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
124 / 131
Input
Lire dans un fichier
Exceptions
Exceptions
Exceptions
Nous allons décrire le mécanisme d’exception d’Objective Caml.
# let tete = function
[] -> failwith "tete"
| x::_ -> x;;
val tete : ’a list -> ’a = <fun>
# failwith;;
- : string -> ’a = <fun>
# tete [];;
Uncaught exception: Failure "tete".
Les exceptions
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Exceptions
février 2013
125 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Exceptions
Exceptions
Le type des exceptions
février 2013
126 / 131
Exceptions
Levée d’une exception
La levée d’une exception se fait à l’aide de la fonction raise
En OCaml, les exceptions appartiennent à un type énuméré prédéfini exn.
I
Le constructeur Failure est un constructeur d’exception avec une string
comme argument
# Failure "hello !";;
- : exn = Failure "hello !"
Remarquez le type de raise
# raise;;
- : exn -> ’a = <fun>
La syntaxe pour définir de nouvelles exceptions est
# exception Erreur;;
exception Erreur
# Erreur;;
- : exn = Erreur
I
I
Le type des exceptions a une particularité
les autres types énumérés n’ont pas cette possibilité.
L’opérateur exception étend l’ensemble des valeurs de exn en déclarant un
nouveau constructeur.
On définit comme cela des nouvelles exceptions.
Programmation Fonctionnelle
Pourtant, pour tout exception ex, l’expression raise ex a un type ’a
quelconque.
Cela permets d’utiliser raise n’importe ou dans une expression, sans casser le
type de l’expression.
Mais comme on fait une fonction à renvoyer une valeur de type ’a pour tout
type ’a, même ceux qui ne sont pas encore défini ?
exn est un type énuméré extensible
S. Guerrini (LIPN - Paris 13)
# let tete = function
[] -> raise (Failure "tete")
| x::_ -> x;;
val tete : ’a list -> ’a = <fun>
février 2013
127 / 131
En fait raise ne termine jamais régulièrement !
raise ne renvoie jamais une valeur, mais termine toujours en levant une
exception.
Pourtant, raise n’as pas besoin d’une valeur de type ’a.
Le fait que raise est une fonction avec type d’output ‘a est un artifice qui
permet de l’utiliser sans casser l’inférence des types.
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
128 / 131
Exceptions
Exceptions
Exceptions
Des exemples
Exceptions
Capture des exceptions
# let tete = function
[] -> raise Erreur
| x::_ -> x;;
val tete : ’a list -> ’a = <fun>
En cas de levée d’une exception, la capture de l’exception permet de
continuer un calcul.
# exception Trouve of int;;
exception Trouve of int
On peut utiliser les captures d’exception comme un véritable style de
programmation.
# Trouve 5;;
- : exn = Trouve 5
La construction try-with permet de le réaliser les captures.
Les di↵érentes étapes à l’exécution sont les suivantes.
I
# raise (Trouve 7);;
Uncaught exception: Trouve 7.
I
I
# exception Erreur_fatale of string;;
exception Erreur_fatale of string
I
Essayer de calculer l’expression.
Si aucune erreur n’est déclenchée, on retourne l’expression.
Si cette évaluation déclenche une erreur qui tombe dans un cas de filtrage,
alors on retourne la valeur correspondante de la clause sélectionnée par le
filtrage.
En cas d’échec sur le filtrage, la valeur exceptionnelle est propagée.
# raise (Erreur_fatale "cas non prevu");;
Uncaught exception: Erreur_fatale "cas non prevu".
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
Exceptions
février 2013
129 / 131
Exceptions
Try-with
Une fonction qui cherche une valeur qui vérifie un prédicat p : ’a -> bool
# let rec ch p n = function
[] -> raise Erreur
| x::l -> if (p x)
then raise (Trouve n)
else ch p (n+1) l;;
val ch : (’a -> bool) -> int -> ’a list -> ’b = <fun>
La fonction lève l’exception Erreur si on ne trouve pas de valeurs, ou l’exception Trouve n
quand on trouve une valeur tel que p n est vrai.
On utilise try-with pour gérer les exceptions :
# let cherche p l =
try (ch p 1 l) with
Erreur -> raise (Failure "cherche: rien trouve")
| Trouve n -> n;;
val cherche : (’a -> bool) -> ’a list -> int = <fun>
I
Si on trouve une valeur :
# cherche pair [1;5;3;4;6;7];;
- : int = 4
I
Si on ne trouve pas de valeur :
# cherche pair [1;5;3];;
Uncaught exception: Failure "cherche: rien trouve".
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
131 / 131
S. Guerrini (LIPN - Paris 13)
Programmation Fonctionnelle
février 2013
130 / 131
Téléchargement