Vous êtes sur la page 1sur 129

Introduction au Langage Caml

SOMMAIRE
INTRODUCTION

Caml (Categorical Abstract Machine Language) est un langage


de programmation développé par l’INRIA depuis 1985

Il se range dans la catégorie des langages fonctionnels


(evaluation de fonctions mathématiques).

C’est un langage fortement typé


Quelques calculs de valeurs
On commence par rappeler un résultat essentiel : 1+1 est bien égal à 2.

1 + 1 ;;
- : int = 2

Même sur un exemple aussi... trivial, il y a plusieurs choses à dire.


D’abord, comment en arrive-t-on là ?
• On a rentré l’expression 1 + 1 dans l’éditeur.
• On a terminé cette expression par un double point-virgule ;;
• C’est obligatoire !
• On a confirmé qu’on voulait le résultat par un appui sur Entrée ou Ctrl-Entrée.
Quelques calculs de valeurs
On commence par rappeler un résultat essentiel : 1+1 est bien égal à 2.

1 + 1 ;;
- : int = 2

Que nous apprend ce résultat ?

• On s’est contenté de calculer une valeur.


C’est en effet ce qu’indique le tiret - par lequel débute la réponse de Caml.

• Cette valeur appartient au type entier, et cette valeur c’est évidemment 2 (le
symbol “ : ” doit effectivement être compris comme la marque
d’appartenance à un type).
Quelques calculs de valeurs
Calculons maintenant d’autres valeurs :

3.14159 ;; (∗ ici on évalue un flottant ∗)


- : float = 3.14159 (∗ c’est-à-dire un nombre réel avec un point décimal ∗)
"bonjour" ;; (∗ ici on évalue une chaîne de caractères ∗)
- : string = "bonjour"
1 < 2 ;; (∗ on forme un booléen: a-t-on 1 < 2 ? (...suspense...) ∗)
- : bool = true (∗ il n’y a que deux booléens : true ou false ∗)
`z`;; (∗ ici on forme un caractère ∗)
- : char = `z` (∗ attention: ce sont deux accents graves ∗)
sin ;; (∗ c’est la fonction intégrée sinus ∗)
- : float -> float = <fun> (∗ et à un flottant elle associe un flottant ∗)
(∗ son contenu n’est pas affiché car codé en langage machine ∗)
() ;; (∗ () est l’unique valeur du type unit ∗)
- : unit = ()
Quelques calculs de valeurs
L’exemple de la fonction sin montre qu’en Caml, les fonctions sont des valeurs
comme les autres.
L’existence du type unit, dont la seule valeur, notée ( ), est “le rien” ou “le vide”,
peut surprendre.
En fait le type unit est utilisé pour les fonctions qui ne prennent pas d’argument, et
à chaque fois que le rôle de l’instruction est de réaliser ce qu’on appelle un “effet”
(voir plus loin).
Un effet typique est l’affichage d’un message. Sa valeur est donc le “rien” () du
type unit.

print_string "bonjour\n";; (∗ afficher bonjour et un retour à la ligne ∗)


bonjour
- : unit = () (∗ retour à la boucle interactive ∗)
Les définitions de constantes globales
On crée une constante au moyen de l’instruction let

La syntaxe d’une liaison globale est let name = expression;;


let a = 1789*2010;;
a : int = 3595890

Tant qu’on ne redéfinit par la constante a, elle reste liée à la valeur 3595890 :
a/10 ;;
- : int = 359589

On peut bien sur utiliser la constante a pour définir d’autres constantes :


let b = 100*a;;
b : int = 359589000

Rien n’empêche de placer dans la constante a une nouvelle valeur calculée à partir de
l’ancienne :
let a = a+1;;
a : int = 3595891
Les définitions de constantes globales

En particulier, on ne peut jamais évaluer un identificateur qui n’est pas lié,


c’est- à -dire qui n’a pas encore été défini par un let

2+c*3;; Ici Caml nous indique une erreur au


“Toplevel”,
Toplevel input: c’est-à-dire au niveau de la boucle
> 2+c*3;; interactive.
>^ Le caractère ˆ montre où il a trouvé
cette erreur, qui signifie ici :
The value identifier c is unbound.
l’identificateur c n’est pas lié.
On peut très bien redéfinir un
. identificateur et changer le type de
son contenu
Les définitions de constantes globales

On peut très bien redéfinir un identificateur et changer le type de son contenu.

let a = "maintenant je suis de type string";;


a : string = "maintenant je suis de type string«

Attention : Caml différencie les majuscules et les minuscules.

On peut définir plusieurs constantes dans la même instruction:

let a = 1 and b = 3.14 and c = "hello";;


a : int = 1
b : float = 3.14
c : string = "hello"
Les définitions de constantes globales
Attention à l’exemple suivant ! ! ! !
let a =-1;; Il est toujours recommandé de mettre un
Toplevel input: espace après le signe =, et c’est même
indispensable pour les nombres négatifs
> let a =-1;;
(sinon : syntax error).
> ^^ Ici il est recommandé d’écrire : let a = -1,
Syntax error. même si let a= -1 ou let a=(-1)
conviennent.
Liaisons locales
La construction “let name = value in expr”
La syntaxe d’une liaison locale à une expression est
let name = value in expr

Quand une telle instruction est exécutée, l’expression value est


d’abord calculée (évaluée) et la valeur obtenue est liée à
l’identificateur name pendant le temps que dure l’évaluation de
l’expression expr (et uniquement pendant ce temps-là).

A l’issue de l’évalutation de expr, la liaison entre name et value est


abandonnée. L’identificateur name retrouve alors la liaison qui était la
sienne avant cette instruction let (et s’il n’en avait pas, il se retrouve
non lié).
La construction “let name = value in expr”
Exemples :

Ici on lie le nom x à la valeur 1 pour le temps que


let x = 1 in x+4;;
dure l’évaluation de l’expression x + 4.
- : int = 5
x;; Le résultat est bien sûr la valeur 5.
Toplevel input:
> x;; Avant ce let, l’identificateur x était visiblement
>^ non lié, comme le montre l’erreur rencontrée
The value identifier x is unbound. quand on cherche à évaluer x.
let x = "bonjour";;
x : string = "bonjour" Seule différence avec l’exemple précédent, x était
let x = 1 in x+4;; lié à ”bonjour” par un let précédent.
- : int = 5
Après l’exécution de “let x=1 in ...”, pendant
x;;
laquelle x est temporairement lié à la valeur 1,
- : string = "bonjour" l’identificateur x retrouve sa liaison d’origine.
Constructions “let name = value in expr” imbriquées

La valeur renvoyée par “let name = value in expr” est bien sûr celle de expr, et il est
possible que expr résulte elle-même d’une construction “let name2 = value2 in expr2”.

let y = 2 in let x = y+1 in x+4;;


- : int = 7

y est lié à 2 pendant l’évaluation de “let x=y+1 in x+4”, qui doit donc être
compris comme “let x=3 in x+4” et qui renvoie bien sûr la valeur 7.
La construction “expr where ...”
La construction “expr where name = value” équivaut à “let name = value in expr”

Avec des let in ... ... ... et avec des where


let x = 1 in x+4;; x + 4 where x = 1;;
- : int = 5 - : int = 5
x + 4 where x = y+1 where y = 2;;
let y = 2 in let x = y+1 in x+4;; - : int = 7
- : int = 7
Opérations et fonctions usuelles sur
les types de base
Definition d’un type

En Caml, un type, c’est un ensemble de valeurs auquel on a donné un nom.


Les types de base sont : int float char string bool unit
Opérations et fonctions sur le type int
Le type int contient les entiers de l’intervalle [−2^30, 2^30 − 1] = [−1073741824,
1073741823].
Cet intervalle contient donc 2^31 = 2147483648 entiers.
Si le résultat d’un calcul tombe hors de intervalle, il y est ramené par périodicité (T = 2^31) :

let a = 1073741823;; (∗ 2ˆ30 − 1: c’est le plus grand élément du type int ∗)


a : int = 1073741823
a+1;; (∗ on lui ajoute 1 et ... ∗)
- : int = -1073741824 (∗ ... on obtient le plus petit élément du type int ∗)
2*a;; (∗ théoriquement 2*a = 2ˆ31 − 2... ∗)
- : int = -2 (∗ modulo 2ˆ31, on obtient en fait −2 ∗)

Les opérations arithmétiques sur le type int sont :


+ (addition), − (soustraction), ∗ (produit), / (quotient entier), mod (modulo, reste entier).
Opérations et fonctions sur le type int
123458 / 4567;;
- : int = 27 (∗ le quotient entier dans la division euclidienne ∗)
123458 mod 4567;;
- : int = 149 (∗ le reste dans la division euclidienne ∗)

Remarque importante : il n’y a pas d’opération puissance sur les entiers !

Voici quelques fonctions utiles sur les entiers :

• min a b (resp. max a b) : donne le minimum (resp. le maximum) de deux entiers a et b.


• abs a : calcule la valeur absolue de l’entier a.
• random__int n : renvoie un entier pseudo-aléatoire dans l’intervalle [0, n−1] (ou dans
l’intervalle [n+1, 0] si n est négatif). Bien noter les deux caractères consécutifs.

Attention ! on peut écrire “random int(n)”, mais on doit écrire “min a b” et non “min(a,b)”.
Opérations et fonctions sur le type float
Le type float est formé des décimaux d’un intervalle qui dépend du système
d’exploitation.
Pour être identifié comme un float, un nombre doit contenir un point décimal et/ou la
spécification d’une puissance de 10 avec e ou E :

1234.56789;;
1;;
- : float = 1234.56789 - : int = 1 (∗ ici c’est un entier ∗)
1234e-7;; 1.;;
- : float = 0.0001234 - : float = 1.0 (∗ ici c’est un décimal !! ∗)

Dans le type float, les opérations arithmétiques sont :


+. (addition), −. (soustraction), ∗. (produit), /. (quotient), ∗∗ ou ∗∗. (puissance)

Remarque : la syntaxe power x y équivaut à x ** y


Opérations et fonctions sur le type float
Voici maintenant quelques fonctions utiles sur les flottants :

• min x y (resp. max x y ) donne le minimum (resp. le maximum) des deux flottants x et y
• abs_float x calcule la valeur absolue du flottant x
• random__float x renvoie un flottant pseudo-aléatoire dans l’intervalle [0, x[ (ou dans
l’intervalle ]x, 0] si x est négatif). Attention aux deux caractères consécutifs.

• Voici les fonctions mathématiques usuelles (argument : un float, le résultat : un float)


exp log (logarithme népérien) sqrt (racine carrée)
sin cos tan asin acos atan sinh cosh tanh
floor (partie entière inférieure) et ceil (partie entière supérieure)
Opérations et fonctions sur le type char
On sait que le type char permet de représenter les “caractères”.
On écrit un char en l’entourant de deux accents graves (touche Alt Gr+è du clavier) :
Par exemple `Z` pour le caractère Z.

Le “code ascii” est une correspondance standardisée entre les caractères et les entiers de 0
à 128. Ce codage s’étend à l’intervalle [129, 255], notamment pour les caractères accentués,
mais cette extension dépend du système d’exploitation et de la langue utilisée.

On peut toujours représenter un caractère par son code ascii en écrivant `\nnn`

On dispose des fonctions de conversion int_of_char (d’un caractère en son code ascii) et
char_of_int (d’un entier, représentant un code ascii, en le caractère correspondant).

Comme on le voit ci-dessous, l’intervalle de définition de char_of_int est [0, 255].


char_of_int 0;;
int_of_char `A`;; char_of_int 125;;
- : char = ‘\000‘
- : int = 65 - : char = `}`
char_of_int 256;;
int_of_char `a`;; ‘\125‘;; Uncaught exception:
- : int = 97 - : char = `}` Invalid_argument "char_of_int"
Opérations et fonctions sur le type string
Un objet de type string est une succession de caractères, délimitée par des
guillemets : “Hé !”.
En particulier chaque caractère dans une chaîne est indicé par sa position, et (c’est
important !) le premier caractère a pour indice 0.

On dispose des fonctions suivantes :

• Création d’une chaîne avec make_string


Avec make_string n c on crée une chaîne de taille n par la répétition d’un
caractère c.

make_string 7 `a`;;
-: string = "aaaaaaa“

• Longueur d’une chaîne str avec string_length str

string_length "abc";;
-: int = 3
Opérations et fonctions sur le type string
• Lecture du caractère de position p dans une chaîne str avec str.[ p ]
let machaine = "abcdefghijk";;
machaine : string = "abcdefghijk"
machaine.[0];;
- : char = `a`
machaine.[string_length machaine - 1];;
- : char = `k`

• Ecriture du caractère c en position p de la chaîne str avec str.[ p ]<- c


Attention, le résultat est le “rien” () du type unit.
machaine.[1] <- `Z`;;
- : unit = ()
machaine;;
- : string = "aZcdefghijk"
Opérations et fonctions sur le type string
• Concaténation de chaînes avec l’opérateur ^ (accent circonflexe) str1^str2^· · ·^strn
"abc"^"de"^"fghi";;
- : string = "abcdefghi"
Remarque : il y a une autre syntaxe possible, en utilisant la fonction concat, dont
l’argument doit être une liste de chaînes (voir plus loin pour la définition et l’utilisation
des listes).
concat ["abc";"de";"fghi"];;
-: string = "abcdefghi“

• Extraction d’une sous-chaîne avec sub_string : avec sub_string str p n on extrait


de la chaîne str la sous-chaîne commençant à la position p et formée de n caractères.
sub_string "abcdefghij" 2 5;;
- : string = "cdefg"
Opérations et fonctions sur le type string
• Transformation d’un caractère car en chaîne avec string_of_char c
string_of_char `Z`;;
- : string = "Z“

Remarque: La commande print_newline () effectue un retour à la ligne


Opérations et fonctions sur le type bool
On sait que le type bool ne contient que les deux valeurs true et false.
• Les opérations sur les booléens sont :

&& (le “et” logique) || (le “ou” logique) not (la négation logique)

• Les opérateurs < , <= , > , >= permettent de comparer (de la façon
habituelle) deux objets d’un même type (ce type pouvant être int, float,
char, string), et le résultat est un booléen.

• On peut même comparer true et false, sachant que false < true

2+6 <= 3+4;; "abc" < "abc ";; ‘a‘ <= ‘A‘;;
- : bool = false - : bool = true - : bool = false
"après" < "avant";; " abc" > "abc";; false < true;;
- : bool = true - : bool = false - : bool = true
Opérations et fonctions sur le type bool
Ces comparaisons s’étendent aux produits cartésiens, en adoptant l’ordre lexicographique
(donc de gauche à droite, la première différence décidant de la comparaison).

Il faut ici que les deux produits cartésiens comparés soient du même type (donc même
longueur, et les éléments ayant la même position doivent être de même type).

(1,"abc",3.,5) < (1,"abc",4.,0);; (∗ ici la décision se fait sur la troisième composante ∗)


- : bool = true

(1,7,2,6) < (1,7,1,9,15);; (∗ ici cela ne marche pas, car les deux produits cartésiens ∗)
Toplevel input: (∗ n’ont pas le même type: en effet, bien qu’ils soient ∗)
> (1,7,2,6) < (1,7,1,9,15);; (∗ composés d’entiers, ils n’ont pas la même longueur ∗)
> ^^^^^^^^^^
This expression has type int * int * int * int * int,
but is used with type int * int * int * int
Quelques fonctions d’impression et de conversion
Caml propose quelques fonctions de conversion entre types de base.
Le nom de ces fonctions est toujours : “typedarrivée_of_typededépart”
On trouve ainsi les opérations de conversion suivantes :

int_of_float (d’un flottant vers un entier) float_of_int (d’un entier vers un flottant)
string_of_int (d’un entier vers une chaîne) int_of_string (d’une chaîne vers un entier)
string_of_float (d’un flottant vers une chaˆıne) float_of_string (chaîne vers flottant)
string_of_char (d’un caractère vers une chaîne) string_of_bool (booléen vers chaîne)

int_of_float 1.8;; La conversion du type float vers le type int se fait toujours
en ne gardant que ce qui précède le point décimal.
- : int = 1
int_of_float 1.8e7;; Il ne s’agit donc ni de partie entière, ni d’arrondi à l’entier
- : int = 18000000 le plus proche.
int_of_float 1.8e9;;
- : int = -347483648 Comme on le voit ici, la conversion peut donner des
int_of_float 1.8e10;; résultats imprévisibles quand on dépasse l’intervalle de
- : int = 0 représentation des entiers.
int_of_float (-1.8);;
Attention à ne pas oublier la parenthèse devant le signe -
- : int = -1
dans le cas d’un float négatif.
Opérations et fonctions sur le type bool
Ces comparaisons s’étendent aussi aux vecteurs et aux listes (voir plus loin
pour la définition et l’utilisation de ces types d’objet), toujours en adoptant
une lecture “de gauche à droite”.
Une deuxième différence est que les deux vecteurs (ou les deux listes)
comparées n’ont pas nécessairement la même longueur.

[1;7;2;6] < [1;7;1;9;15];; (∗ comparaison entre deux listes d’entiers ∗)


- : bool = true
[|1;7;2;6|] < [|1;7;1;9;15|];; (∗ comparaison entre deux vecteurs d’entiers ∗)
- : bool = true
Quelques fonctions d’impression et de conversion

Il existe aussi plusieurs fonctions d’impression à l’écran.


Le nom de ces fonctions s’écrit toujours “print_nomdutype”

Fonctions d’impression : print_int print_float print_char print_string print_bool

Les impressions sont des “effets”. Leur résultat est donc le “rien” () du type unit.
print_int 2010 ; print_float 3.14159 ; print_string “XY" ; print_char `Z` ;;
20103.14159XYZ- : unit = ()
Produits cartésiens de types

Pour définir des types à partir de ces types de base, on utilise des constructeurs

Par exemple, le constructeur “,” (la virgule) sert à construire les éléments de
produits cartésiens de types.

De même qu’en mathématiques, on définit le produit cartésien E1 × E2 × · · ·En


de n ensembles,
on définit en Caml le produit cartésien noté typ1*typ2*· · ·*typn de n types
typ1, . . . ,typn
Produits cartésiens de types
La syntaxe, pour créer un objet appartenant à un tel produit cartésien, est de
séparer ses différentes composantes par des virgules (Caml se chargeant de
déterminer les types respectifs).
Il n’est pas nécessaire (mais ça peut aider à la lecture) de placer le tout entre
parenthèses.
Ici on forme la constante couple de type int * string
On forme ensuite le quadruplet nuple, de type float * float * bool * char

let couple = 123,"bonjour";;


couple : int * string = 123, "bonjour“

let nuple = 4.15, 3.19, true, ‘Z‘;;


nuple : float * float * bool * char = 4.15, 3.19, true, ‘Z‘
Produits cartésiens de types
Pour les couples (donc les éléments d’un produit cartésien de deux types) les
fonctions fst en snd permettent respectivement d’accéder au premier et au
deuxième élément du couple :

fst couple;; snd couple;;


- : int = 123 - : string = "bonjour“
Produits cartésiens de types
Attention, toutes les déclarations suivantes conduisent à des objets de types
différents :
let obj1 = (4.15, 3.19, true, ‘Z‘);;
obj1 : float * float * bool * char = 4.15, 3.19, true, ‘Z‘
let obj2 = 4.15, (3.19, true, ‘Z‘);;
obj2 : float * (float * bool * char) = 4.15, (3.19, true, ‘Z‘)
let obj3 = (4.15, 3.19), (true, ‘Z‘);;
obj3 : (float * float) * (bool * char) = (4.15, 3.19), (true, ‘Z‘)
let obj4 = 4.15, (3.19, (true, ‘Z‘));;
obj4 : float * (float * (bool * char)) = 4.15, (3.19, (true, ‘Z‘))

En utilisant la définition précédente de obj4, on peut écrire :


fst (snd (snd obj4));;
- : bool = true
Produits cartésiens de types
Un aspect intéressant des n-uplets est qu’on peut opérer simultanément plusieurs
liaisons.
C’est aussi un moyen commode d’échanger les valeurs liées à deux identificateurs.

let a,b,c = 36,"hello",3.14;; let b,a = a,b;;


a : int = 36 b : int = 36
b : string = "hello" a : string = "hello"
c : float = 3.14
Instruction Conditionnelle
Si {un ensemble de conditions} est vérifié,
alors {effectuer un ensemble d'opérations}
Sinon, {effectuer un autre ensemble d'opérations}

if {conditions} then {action1} else {action2}


(Toutes les actions doivent être de même type)
Exemple
If 2<3 then print_string "bonjour“ else print_string “bonsoir“;;

If 1/2==0 & not true==false then 2+1 else 2*3;;

Remarque: S’il n’y a qu’une seule action, if condition then action


cette action doit être de type unit
Instruction Conditionnelle
S’il y plus de deux conditions , on pourra utiliser else if

if condition1 then condition1


else if condition2 then action2

else if conditionN then actionN
else expr-si-faux
Exemple:

let a =2;;
if a>2 then “positif“
else if a<0 then “negatif“
else “ nulle”;;
Introduction aux fonctions
(non récursives)
Fonctions à un seul argument
Au sens Caml, une fonction est un mécanisme qui permet d’associer à un objet
d’un type de départ un objet d’un type d’arrivée.

Pour définir une fonction, on utilise le mot réservé function et le constructeur ->
let f = function x -> x+1;;
f : int -> int = <fun> on définit ici une fonction nommée f, qui va du
type int vers lui-même.

Le type de la valeur f est donc “fonction de int vers int”.


Il a été inféré par l’interpréteur Caml, au vu de l’opération “+” qui n’opère que dans
le type int.
Après le signe “=”, la description du contenu de l’objet f se réduit à <fun> (car le
contenu d’une fonction, codé en langage machine, n’est pas lisible).
Fonctions à un seul argument
On peut alors calculer des images par l’application f.
f 9;; (∗ ou encore f(9): on calcule donc ici l’image de l’entier 9 ∗)
- : int = 10 (∗ le résultat est évidemment l’entier 10 ∗)
f 9.;; (∗ mais si on cherche l’image du flottant 9. ∗)
Toplevel input: (∗ on se heurte à une erreur de type ∗)
> f 9.;;
> ^^
This expression has type float,
but is used with type int.
Fonctions à un seul argument
Remarque importante : Caml propose une écriture simplifiée de la définition d’une
fonction. Ainsi, plutôt que “let f = function x -> ...;; ” on écrira “let f x = ...;; ”

let f x = 2*x;; (∗ on définit la fonction qui double un entier ∗)


f : int -> int = <fun>
f 2+3;; (∗ ici Caml va comprendre f(2) + 3 ... ∗)
- : int = 7 (∗ ... comme si on avait écrit f(2)+3, ou (f 2)+3 ∗)
f(2+3);; (∗ d’où l’importance des parenthèse ses dans ce cas ∗)
- : int = 10
f(-5);; (∗ ici on calcule f(−5) ∗)
- : int = -10
f -5;; (∗ sans les parenthèses, Caml comprend ... ∗)
Toplevel input: (∗ ... qu’on veut évaluer la différence f − 5 ∗)
> f -5;; (∗ bien sûr cela provoque une erreur ... ∗)
>^ (∗ ... car f n’est pas un entier mais une fonction ∗)
This expression has type int -> int,
but is used with type int.
Fonctions “à plusieurs variables” (non curryfiées)
Définition “non curryfiée” d’une fonction f à n variables :
let f (x1,x2,· · ·,xn) = expr;;

Une fonction qui reçoit ses un argument à la fois sous la


forme d’un n-uplet de valeurs est dite non curryfiée
Fonctions “à plusieurs variables” (non curryfiées)
Définition “non curryfiée” d’une fonction f à n variables :
let f (x1,x2,· · ·,xn) = expr;;

Dans la définition, les parenthèses sont nécessaires pour que Caml isole l’unique
argument de f.

Caml analyse expr et détermine les types respectifs typk des xk et le type typa de expr.
Le type de f est alors : typ1 * typ2 * ...* typn -> typa
Prenons un exemple simple :

let f (x,y,z) = x+y > 2*z;;


f : int * int * int -> bool = <fun>

Ici Caml voit que l’expression finale est une comparaison (le type d’arrivée est donc bool )
et que les termes de cette comparaison imposent le type int aux identificateurs x, y, z.
Fonctions “à plusieurs variables” (non curryfiées)

On peut alors utiliser l’application f :


f (4,11,7);;
- : bool = true

Là encore, les parenthèses étaient nécessaires : sans elles Caml tenterait


d’appliquer f à 4 puis de former le triplet (f(4), 11, 7) ce qui n’a pas de sens car
le type de départ de f n’est pas int.

f 4,11,7;;
Toplevel input:
> f 4,11,7;;
>^
This expression has type int,
but is used with type int * int * int.
Fonctions “à plusieurs variables” (curryfiées)
Définition “curryfiée” d’une fonction f à n variables :
let f x1 x2 · · · xn = expr;;

Une fonction qui reçoit ses arguments un par un est


dite curryfiée
Fonctions “à plusieurs variables” (curryfiées)
Définition “curryfiée” d’une fonction f à n variables :
let f x1 x2 · · · xn = expr;;
Dans cette définition, Caml analyse expr et en déduit les types respectifs typk des
xk, ainsi que le type d’arrivée typa de expr.
Le type de la fonction f est alors : typ1 -> (typ2 -> (· · · -> (typn-> typa)· · ·))
Ou encore, par associativité à droite : typ1 -> typ2 -> · · · -> typn -> typa

Voici la définition d’une fonction f de trois variables entières x, y, z et à valeurs


booléennes, puis celle de sa curryfiée g (on observera bien la différence des types
de f et de g).

let f (x,y,z) = x+y > z;; (∗ fonction à trois variables, non curryfiée ∗)
f : int * int * int -> bool = <fun>
let g x y z = x+y > z;; (∗ g est est la version curryfiée de f ∗)
g : int -> int -> int -> bool = <fun>
Fonctions “à plusieurs variables” (curryfiées)

Bien sûr, on peut définir g connaissant f (en la curryfiant) ou f connaissant g (en la


décurryfiant).

let f (x,y,z) = x+y > z;; let g x y z = x+y > z;;


f : int * int * int -> bool = <fun> g : int -> int -> int -> bool = <fun>
let g x y z = f (x,y,z);; let f (x,y,z) = g x y z;;
g : int -> int -> int -> bool = <fun> f : int * int * int -> bool = <fun>
Fonctions “à plusieurs variables” (curryfiées)
Le type de g est donc int -> (int -> (int -> bool)).
Autrement dit, g est une fonction qui à un entier x associe la fonction qui à un
entier y associe la fonction qui à l’entier z associe un booléen (en l’occurence
l’expression booléenne (x + y) > z).
Cela explique pourquoi on dispose de plusieurs versions équivalentes de
l’expression g x y z (dans un cas aussi simple on préfèrera naturellement la version
sans parenthèses)

f (4,11,7);; (g 4) 11 7;; g(4)(11)(7);;


- : bool = true - : bool = true - : bool = true
g 4 11 7;; (g 4 11) 7;; ((g(4))(11))(7);;
- : bool = true - : bool = true - : bool = true

Il est important de comprendre qu’ici l’application f prend obligatoirement ses trois


arguments à la fois (sous la forme d’un triplet), et que l’application g les prend
chro-no-lo-gi-que-ment !
Fonctions “à plusieurs variables” (curryfiées)

Plus précisément, et c’est l`a tout l’intérêt des définitions curryfiées, on peut ne
fournir que le premier argument (ou les deux premiers arguments) de g :

let u = g 4;; (∗ la fonction qui à un entier y associe la fonction ... ∗)


u : int -> int -> bool = <fun> (∗ ... qui à un entier z associe le booléen 4 + y > z ∗)
let v = g 4 11;; (∗ fonction qui à z entier associe le booléen 15 > z ∗)
v : int -> bool = <fun> (∗ on aurait d’ailleurs pu écrire let w = u 11;; ∗)

Très important : si f est une fonction, l’expression “f x” est interprétée comme f(x).
De même l’expression “f x y” sera interprétée comme (f(x))(y) , et ainsi de suite.
Fonctions “à plusieurs variables” (curryfiées)

L’association entre une fonction et un élément de son ensemble de départ est


donc une opération associative à gauche.
Cela explique l’erreur suivante, où on tente de définir f : x → sin(cos(x))

let f x = sin cos x;; Quand Caml évalue sin cos x, il


Toplevel input: applique la fonction sin à la fonction
> let f x = sin cos x;; cos .
> ^^^ Or sin s’applique à un flottant, et cos
n’est pas du type float.
This expression has type float -> float, Il faut donc écrire :
but is used with type float. let f x = sin (cos x);;
Une variante de syntaxe
Pour la définition des fonctions à un argument, ou curryfiées à plusieurs
arguments, Caml possède une variante de syntaxe avec le mot réservé fun

On pourra définir une fonction curryfiée f prenant n arguments


x1, x2, . . . , xn des deux manières équivalentes suivantes :

let f x1 x2 · · · xn = expr ;;
ou bien
let f = fun x1 x2 · · · xn -> expr ;;
Une variante de syntaxe
Pour prendre quelques exemples très simples, les définitions suivantes sont
équivalentes :
let f = function x -> x+1;;
let f = fun x -> x+1;;
let f x = x+1;;

De même, les définitions suivantes sont équivalentes :


let f = function x -> function y -> function z -> x*y+z;;
let f = fun x y z -> x*y+z;;
let f x y z = x*y+z;;
let f x y = function z -> x*y+z;;
Une variante de syntaxe
les mots réservés function et fun permettent de définir des fonctions anonymes,
c’est-à-dire des valeurs de type fonction auxquelles on n’aurait pas donné de nom.

Par exemple (s’il y a plusieurs variables, on voit qu’un seul appel à function ne
convient pas) :

(function x -> 2*x) 3;;


(function x y -> 100*x+y) 4 27;;
- : int = 6
Toplevel input:
(fun x -> 2*x) 3;;
> (function x y -> 100*x+y) 4 27;;
- : int = 6
> ^^^
(fun x y -> 100*x+y) 4 27;;
The constructor x is unbound.
- : int = 427
Filtrage
Filtrage
• Le filtrage

Le filtrage a le même rôle que les tests conditionnels : il vérifie une ou


plusieurs conditions, puis selon les cas effectue tel ou tel bloc
d'instructions

(* exemple *)
match {variable} with
| {valeur1} -> {instructions1} let est_nul x = match x with
| {valeur2} -> {instructions2} | 0 -> true
| _ - > false;;
...

Remarque:
Le filtrage peut s'effectuer n'importe où et dans n'importe quelle
fonction, pas seulement dans les fonctions récursives.
Filtrage
Dans le filtrage, il est également possible d'effectuer plusieurs vérifications avant
d'effectuer un bloc d'opérations à l'aide du mot clé when

(* exemple 1*)
let est_pair x = match x with
| x when x mod 2 = 0 -> true
| _ -> false;;

(*exemple 2*)
let est_pair x = match (x mod 2) with
| 0 -> true
| _ -> false;;

(*exemple 3*)
let est_pair x =
if x mod 2 = 0 then true
else false;;
Filtrage
Recursivité
Récursivité
• Les fonctions récursives

La récursivité est une notion se rapportant aux fonctions. Une fonction récursive est
une fonction qui s'appelle elle-même.
En d'autres termes, on utilise la fonction à l'intérieur d'elle-même.
On les définit à l’aide du mot clé rec

let rec fonction {paramètres} = {instructions};;


Exemple: la fonction factorielle

let rec fact n =


if n=0 then 1
else n*(fact (n-1));;

fact 3;; (∗ appel de la fonction∗)


Récursivité
• Exemple
let rec fact n = match n with
| 0 -> 1
| n -> n*(fact (n-1));;

Autre façon L’underscore _ a la signification de


let rec fact n = match n with « toute autre forme et valeur de n »

| 0 -> 1
| _ -> n*(fact (n-1));;

let rec fact = function Si vous utilisez la syntaxe avec


function vous pouvez vous omettre
| 0 -> 1 de préciser les paramètres et le
| n -> n * (fact (n-1));; match
Récursivité
• Tracer ces fonctions
La fonction trace permet de suivre les appels récursifs de
fonctions, elle permet de "suivre à la trace" vos fonctions

exemple:

trace "fact";;

fact 5;;
Recursivité mutuelle
ou croisée
Récursivité mutuelle ou croisée
Il est possible de définir deux fonctions s’appelant
mutuellement.
Il suffit pour cela d’utiliser une définition simultanée avec le mot
réservé and (sans oublier bien sûr le rec dans let rec).

Pour définir deux fonctions f et g mutuellement récursives on


utilise la construction

let rec f = e1 and g = e2


Récursivité mutuelle ou croisée
let rec pair n = match n with
| 0 -> true
| n -> impair(n-1)
and impair n = match n with
| 0 -> false
| n -> pair(n-1);;

pair : int -> bool =<fun>


impair : int -> bool = <fun>
Récursivité terminale
et non terminale
Récursivité terminale et non terminale

On distingue deux types de fonctions récursives :


• Les fonctions récursives terminales
• Les fonction récursive non terminale
Les fonctions récursives terminales
On dit qu’une fonction récursive f est récursive terminale si
la dernière instruction de la définition de f se réduit à un
appel à f elle-même, et si c’est la seule instruction de ce
type dans la définition de f.

Par exemple : le calcul du PGCD

let rec pgcd (a,b) = if b = 0 then a


else pgcd (b,a mod b);;

Pour ces fonctions, on constate qu’il n’est pas nécessaire de


réserver de la mémoire destinée à stocker les résultats des
appels récursifs intermédiaires.
Les fonctions récursives non terminales
Elles utilisent le résultat du dernier appel de la fonction pour
évaluer le résultat de l’appel précédent et on ”remonte” ainsi de
suite jusqu’à obtenir la valeur du premier appel.

Inutile de préciser que les fonctions récursives non terminales


sont les plus gourmandes en temps de calcul.

Par exemple : fonction factorielle


let rec fact n = if n<=0 then 1 else n*fact(n-1);;

En effet, sa dernière instruction ne se limite pas à un appel de


fact mais au produit par n du résultat préalablement calculé de
fact(n−1).
Les fonctions récursives non terminales
On peut parfois rendre ”terminale” une fonction récursive ”non terminale”
en utilisant un accumulateur.
Dans le cas de la fonction n!, on peut :
1. utiliser la formule de récursivité n! = n.(n-1)! qui donne une fonction
récursive non terminale
2. utiliser une fonction auxiliaire aux(n,acc) qui ”accumule” n dans
l’accumulateur (en faisant n × acc) en décroissant n de 1.
— la formule de récursivité est aux(n,acc) = aux(n-1,n.acc) qui
donne une fonction récursive terminale
— il suffit alors d’appeler aux(n,1) pour obtenir n!.
let fact n = let rec aux n acc = match n with
|0 -> acc
|p -> aux (p-1) p*acc
in aux n 1 ;;
Programmation impérative
Programmation impérative

Dans cette section, nous allons étudier certains “traits impératifs” de


Caml.

On verra notamment comment définir puis modifier le contenu de


“cases mémoire”, effectuer des tests avec if then (else) , et répéter
des séquences d’instructions avec les boucles for et while
Les références
Disons qu’une référence est une liaison entre un identificateur et une
“adresse” (ou encore une “boîte”) à laquelle (ou dans laquelle) on trouve un
objet de type donné.

On crée une référence par : let name = ref expr ;;

On peut bien sûr réaliser une liaison locale par un “let name =ref expr in ...”.

Pour accéder à la valeur pointée par la référence, on utilise l’opérateur de


déférencement “ ! ” : on ´évalue donc !name

Pour modifier le contenu de la boîte (et obligatoirement par un objet du type


initial), on utilise la construction “name:= value”.
Les fonctions incr et decr permettent d’incrémenter de 1 (ou de décrémenter
de 1) la valeur (qui est alors nécessairement de type int) pointée par une
référence.
Les références
Exemple

let a = ref 1789 ;; (∗ on crée une référence, nommée a, vers un entier ∗)


a : int ref = ref 1789 (∗ pour l’instant le ‘‘contenu de la boîte’’ est donc 1789. ∗)
!a + 1;; (∗ c’est la valeur pointée par la référence, plus 1 ∗)
- : int = 1790
a := 2010;; (∗ on met 2010 dans la boîte ∗)
- : unit = () (∗ bien noter que le résultat est le ‘‘rien’’ du type unit ∗)
a := 2*(!a);; (∗ on double la valeur référencée par a ∗)
- : unit = () (∗ ne pas oublier les parenthèses, ou écrire a := 2* !a;; ∗)
!a;; (∗ le nouveau contenu de la boîte est 4020 ∗)
- : int = 4020
a;; (∗ si on évalue a, on obtient le type de a... ∗)
- : int ref = ref 4020 (∗ c’est une référence vers un entier, actuellement 4020 ∗).
incr a; a;; (∗ on incrémente l’entier pointé, puis on affiche le résultat ∗)
- : int ref = ref 4021

Remarque importante : Toute modification de la valeur pointée par une référence est un “effet”
(modification du contenu d’une case mémoire) et renvoie donc () du type unit
La définition des vecteurs
En Caml, un vecteur est un tableau, de taille fixée, formé d’objets de même type.
On le définit par la succession de ses éléments, avec la syntaxe :
[| obj1; obj2;. . .; objn |]
Si typ est le type commun aux objk, le type d’un tel vecteur est alors typ vect

Voici quelques définitions de vecteurs :


let v1 = [|5;7;8;2|];; (∗ un vecteur d’entiers ∗)
v1 : int vect = [|5; 7; 8; 2|]
let v2 = [|5.;7.;8.;2.|];; (∗ un vecteur de flottants ∗)
v2 : float vect = [|5.0; 7.0; 8.0; 2.0|]
let v3 = [|"ab";"cde";"f"|];; (∗ un vecteur de chaînes ∗)
v3 : string vect = [|"ab"; "cde"; "f"|]
let v4 = [|‘x‘;‘y‘;‘z‘;‘x‘|];; (∗ un vecteur de caractères ∗)
v4 : char vect = [|‘x‘; ‘y‘; ‘z‘; ‘x‘|]
let v5 = [|true; false; false|];; (∗ un vecteur de booléens ∗)
v5 : bool vect = [|true; false; false|]
La définition des vecteurs
Voici quelques remarques importantes sur les vecteurs.
• On ne peut pas modifier la taille d’un vecteur.
Il est même possible de définir le vecteur vide [||]

• Ne pas confondre les délimiteurs “[|” et “|]” avec “[” et “]” qui définissent les
listes (voir plus loin)

• On peut définir des vecteurs d’éléments d’un type arbitraire.


On peut par exemple des vecteurs de vecteurs :
let v6 = [|[|2;7|];[|6;4;1;9|];[|5;0;3|]|];;
v6 : int vect vect = [|[|2; 7|]; [|6; 4; 1; 9|]; [|5; 0; 3|]|]
On peut même définir des vecteurs de type unit type, comme par exemple
[|();();();()|]
La définition des vecteurs
Bien noter que le séparateur entre deux éléments d’un vecteur est le “point-virgule” ;

On ne le confondra donc pas avec la “virgule” séparant les éléments d’un produit
cartésien.

Ici, par exemple, on définit non pas un vecteur d’entiers (et qui serait de longueur 3),
mais un vecteur de longueur 1 et dont le type est int*int*int vect
let v7 = [|1,5,3|];;
v7 : (int * int * int) vect = [|1, 5, 3|]

De même, on définit ici un vecteur de taille 2, dont les éléments ont le type
int*string
Dans ce cas, il aurait été plus clair d’écrire : let v8 = [|(1,"he");(2,"ho")|];;
let v8 = [|1,"he";2,"ho"|];;
v8 : (int * string) vect = [|1, "he"; 2, "ho"|]
Opérations prédéfinies sur les vecteurs
• vect_length v donne la longueur du vecteur v
• On lit l’élément de v situé en position p par v.(p)
Ainsi le premier élément de v est v.(0) et le dernier est v.((vect_length v)-1)

• On modifie l’élément de v situé en position p par v.(p) <- expr


Attention, le résultat d’une telle instruction est la valeur () du type unit.

• On crée un vecteur formé de n copies de expr par make_vect n expr , et


on crée une matrice de type m × n (en fait un vecteur de m vecteurs de
taille n) formé de mn copies de expr par make_matrix m n expr

• Avec init_vect n f on forme le vecteur [|f(0);f(1);...;f(n−1)|].


Opérations prédéfinies sur les vecteurs

• On concatène deux vecteurs v1 et v2 par concat_vect v1 v2


Il faut bien sûr que les deux vecteurs aient le même type.

• On extrait de v le vecteur débutant à la position p, et de taille n, par


sub_vect v p n

• On crée une copie de v, indépendante de l’original, par copy_vect v

• Dans v, à partir de la position p, on écrit n fois expr par :


fill_vect v p n expr
Attention : la modification est faite “en place”, ce qui signifie que le vecteur
initial est modifié, notamment s’il est sauvegardé dans une variable.
D’ailleurs le résultat de cette instruction est le () du type unit.
Opérations prédéfinies sur les vecteurs

• On convertit une liste ℓ en vecteur par vect_of_list ℓ

• On convertit un vecteur v en liste par list_of_vect v

• On applique une fonction f à tous les éléments d’un vecteur v par


map_vect f v

• On applique une procédure f à tous les éléments d’un vecteur v par


do_vect f v
Exemples d’opérations sur les vecteurs

let v = make_vect 5 0;; (*on définit un vecteur v de taille 5, par répétition de l’entier 0*)
v : int vect = [|0; 0; 0; 0; 0|]
let v’ = v and v’’ = copy_vect v;; (*on copie v dans v’ et v”, v” independante de l’originale)

v’ : int vect = [|0; 0; 0; 0; 0|]


v’’ : int vect = [|0; 0; 0; 0; 0|]
v.(0) <- 5; v.(vect_length v - 1) <- 9; v;; (*on modifie le premier et dernier element de v*)

- : int vect = [|5; 0; 0; 0; 9|]


v’, v’’;; (*On voit enfin que cette modification s’est répercutée sur v′, mais pas sur v′′*)
- : int vect * int vect = [|5; 0; 0; 0; 9|], [|0; 0; 0; 0; 0|]
Exemples d’opérations sur les vecteurs

let a = ref 1 and b = 0;; (*on crée une référence a vers l’entier 1, et la constante b = 0 *)

let v = make_vect 4 (a,b);; (*On crée ensuite un vecteur en répétant quatre fois *)
(* le couple (a, b) *)

v : (int ref * int) vect = [|ref 1, 0; ref 1, 0; ref 1, 0; ref 1, 0|]

a := 10*(!a);; let b = 3;; (* Puis on multiplie par 10 la valeur référencée par a, et on *)


(* réinitialise b avec la valeur 3. *)

v;; (*le contenu de l’adresse mémoire de a a été modifié , mais pas celle de b *)
- : (int ref * int) vect = [|ref 10, 0; ref 10, 0; ref 10, 0; ref 10, 0|]
Séquences d’instructions
Répétitions inconditionnelles (boucles for)

On dispose en Caml des deux expressions suivantes :

for compteur = debut to fin do expr done ;


et
for compteur = debut downto fin do expr done;

for i=1 to 7 do for i=7 downto 1 do


print_int i; print_int i;
print_newline() print_newline()
done;; done;;
Répétitions inconditionnelles (boucles for)
Définition de la fonction sum calculant la somme des éléments d’un tableau

let sumv v = Définition de la fonction sumv, d’argument v

let s = ref 0 On crée une référence locale s vers un entier (au départ 0)
and n = vect_length v in De même, n désigne localement la taille du vecteur
for k = 0 to n-1 do De k = 0 à k = n−1, c’est-à-dire sur toute la longueur de v,
s := !s + v.(k)
on ajoute l’élément de position k à l’entier référencé par s.
done;
Quand c’est terminé, on renvoie l’entier référencé par s, qui
!s;;
représente bien sûr la somme des éléments du tableau.
sumv : int vect -> int = <fun>
sumv [| 5;3;7;11;8|];; On voit finalement un exemple d’utilisation de la fonction sumv

- : int = 34
Répétitions inconditionnelles (boucles for)
Fonction transformant un entier n en la matrice identité d’ordre n.

let id n =
let m = make_matrix n n 0 in
for k=0 to n-1 do m.(k).(k) <- 1 done;
m;;

id : int -> int vect vect = <fun>

id 4;;
- : int vect vect =
[|[|1;0;0;0|];[|0;1;0;0|];[|0;0;1;0|];[|0;0;0;1|]|]
Répétitions inconditionnelles (boucles for)
On peut bien sûr imbriquer les boucles for. Dans l’exemple suivant, on écrit une
fonction calculant la transposée d’une matrice de type quelconque

let transpose m =
let n=vect_length m and p=vect_length m.(0) in (∗ m est une matrice ∗)
(∗ de type (n, p) ∗)
let m’= make_matrix p n m.(0).(0) in (∗ forme une matrice m′ de ∗)
(∗ type (p, n) ∗)
for i=0 to p-1 do (∗ pour chaque ligne i de m′ ∗)
for j=0 to n-1 do (∗ et pour chaque élément de cette ligne ∗)
m’.(i).(j)<-m.(j).(i) (∗ dans m′[i, j] on place m[j, i] ∗)
done; (∗ fin du remplissage de la ligne i ∗)
(∗ de m′ ∗)
done; m’;; (∗ fin du remplissage, et évaluation de m′ ∗)
Répétitions inconditionnelles (boucles for)

transpose : ’a vect vect -> ’a vect vect = <fun>

let a = [|[|7; 3; 4|]; [|6; 2; 1|]|];;


a : int vect vect = [|[|7; 3; 4|]; [|6; 2; 1|]|]

transpose a ;;
- : int vect vect = [|[|7; 6|]; [|3; 2|]; [|4; 1|]|]
Branchements conditionnels avec if then (else)
On dispose en Caml de l’expression conditionnelle suivante :
if condition1 then condition1
else if condition2 then action2

else if conditionN then actionN
else expr-si-faux

let val_abs x = (*fonction valeur absolue*)


if x>0. then x
else -.x;; let min a b = (*fonction minimum*)
if a<=b then a
let max a b = (*fonction maximum*) else b;;
if a<=b then b
else a;;
Branchements conditionnels avec if then (else)

Exercice
1. Ecrire une fonction qui prend en paramètre un entier x, affiche « x est
positif » si l’entier est positif , « x est négatif » , s’il est négatif et « x est
égale à zéro » s’il est égale à 0
2. Ecrire une fonction qui retourne le minimum de trois objets x, y et z
Branchements conditionnels avec if then (else)

Exercice
1. Ecrire une fonction qui prend en paramètre un entier x, affiche « x est
positif » si l’entier est positif , « x est négatif » , s’il est négatif et « x est
égale à zéro » s’il est égale à 0
2. Ecrire une fonction qui retourne le minumum de trois objets x, y et z

let affiche_signe x =
If x>0 then print_int x ;print_string “ est positif”
else if x==0 print_int x ; print_string “égale à zéro”
else print_int x ;print_string “négatif”; ;

affiche_signe (10);;
Branchements conditionnels avec if then (else)

Exercice
1. Ecrire une fonction qui prend en paramètre un entier x, affiche « x est
positif » si l’entier est positif , « x est négatif » , s’il est négatif et « x est
égale à zéro » s’il est égale à 0
2. Ecrire une fonction qui retourne le minimum de trois objets x, y et z

let affiche_signe x =
If x>0 then print_int x ;print_string “ est positif”
else if x==0 print_int x ; print_string “égale à zéro”
else print_int x ;print_string “négatif”; ;

affiche_signe (10);;

let min3 x y z = if x<=y && x<=z then x


else if y<=z then y else z;;

min3 "uv" "abc" "xyz";;


Répétitions conditionnelles (boucles while)

On dispose en Caml de l’expression:

while expression_boolenne do operation done

let cpt = ref 0 in


while !cpt <= 9 do
print_int !cpt;
incr cpt;
done;;
Répétitions conditionnelles (boucles while)
La fonction suivante calcule le pgcd de deux entiers de façon itérative, par
l’algorithme d’Euclide.

let pgcd a b = (∗ définition de la fonction pgcd ∗)

let a’, b’ = ref a, ref b in (∗ deux références vers les entiers de départ ∗)
while !b’ <> 0 do (∗ tant que l’entier pointé par b′ est non nul ∗)

let r = !a’ mod !b’ (∗ soit r le quotient dans la division ∗)

in a’ := !b’; b’ := r (∗ en gros, on utilise pgcd(a′, b′) = pgcd(b′, r) ∗)

done; !a’;; (∗ quand c’est fini, on renvoie l’entier pointé par a′ ∗)

pgcd : int -> int -> int = <fun>


Types personnalisés
Types personnalisés

Pour de nombreux problèmes, il est intéressant de créer


ses propres types avec l’instruction type

• Soit en donnant la réunion de toutes les valeurs


possibles (on parle de type somme)

• Soit en utilisant une sorte de produit cartésien de types


existants (on parle de type produit ou enregistrement)
Types personnalisés
La définition d’un nouveau type s’effectuera toujours avec
l’instruction type, avec la syntaxe suivante:
type nom du type = description_du_nouveau_type ;;
Caml répondra alors par la phrase “Type name defined “

Il est toujours possible de donner un nom supplémentaire à un type


existant (pour abréger, ou parce que cela peut être bien pratique
dans des opérations de filtrage).
Dans ce cas, la syntaxe est la suivante ( bien noter le double == )
type nom_du_type == description_du_type_existant ;;

Par exemple :
type point3d == float * float * float;;
Type point3d defined.
Les types somme
(constructeurs constants)
Les types somme (constructeurs constants)
Une première solution consiste à définir un type par la liste finie de
ses objets (de la même manière qu’on définit un ensemble fini par la
donnée de ses différents éléments).
Pour modéliser un jeu de 32 cartes, on peut par exemple créer le
type couleur et le type hauteur.
Chacun de ces deux types est défini par une succession
d’identificateurs séparés par le caractère | (qu’on peut alors
interpréter comme un opérateur d’ajout de valeurs au type).

type couleur = Trèfle | Carreau | Coeur | Pique;;


Type couleur defined.
type hauteur = Sept | Huit | Neuf | Dix | Valet | Dame | Roi | As;;
Type hauteur defined.
Les types somme (constructeurs constants)
Dans ces définitions, les identificateurs Trèfle, Sept, etc. sont appelés
des constructeurs (ils servent effectivement à construire des objets
du type en question).
Attention : dans cet exemple, on ne peut pas utiliser par exemple
l’entier 9 à la place du constructeur Neuf.

On verra plus loin qu’il est possible de construire des valeurs en


fonction d’un paramètre (celui-ci appartenant
lui-même à un type existant). Ce n’est pas le cas dans les deux
définitions précédentes : on exprime cette situation en disant que
Trèfle, Sept, etc. sont des constructeurs constants

Il est d’usage que le nom d’un constructeur commence par une


majuscule (mais pas le nom d’un type).
Les types somme (constructeurs constants)

On dit que couleur et hauteur sont des types somme car ils
sont définis en tant que sommes (au sens d’ajouts
successifs) d’un certain nombre de valeurs distinctes.

Et ce sont des types somme constants car ils sont définis au


moyen de constructeurs constants.
On parle aussi de types énumérés.

Les constructeurs peuvent apparaître dans une expression


Caml (et par exemple dans des cas de filtrage) mais ils ne
peuvent pas être utilisés comme identificateurs dans une
liaison globale ou locale.
Les types somme (constructeurs constants)
On définit ici la fonction booléenne est une figure sur le type hauteur :

let est_une_figure = function


| Valet | Dame | Roi | As -> true;
| _ -> false;;
est_une_figure : hauteur -> bool = <fun>
est_une_figure Valet;;
- : bool = true

Il est alors facile de modéliser une carte comme un objet de type


hauteur∗couleur.
On peut même donner le nom carte à ce type existant
(attention : ce n’est donc pas la définition d’un nouveau type).

type carte == hauteur * couleur;;


Type carte defined.
Les types somme (constructeurs constants)
De même, on peut nommer main le type des vecteurs d’objets de
type carte.
type main == carte vect;;
Type main defined.
On pouvait même écrire simultanément :
type carte == hauteur*couleur and main == carte vect;;

Définissons la constante atout comme une référence sur une couleur


(initialement Pique) :
let atout = ref Pique;;
atout : couleur ref = ref Pique
Les types somme (constructeurs constants)
On peut alors définir une fonction valeur_carte, définie sur le type
carte, et qui (en fonction de la couleur d’atout) attribue à chaque
carte sa valeur (au sens du jeu traditionnel de la belote).

let valeur_carte (h,c) = match h with


| Neuf -> if c = !atout then 14 else 0
| Valet -> if c = !atout then 20 else 2
| As -> 11 | Dix -> 10 | Roi -> 4 | Dame -> 3 | _ -> 0 ;;
valeur_carte : hauteur * couleur -> int = <fun>

Voici un exemple d’utilisation (en supposant que l’atout est Pique


pour la troisième expression) :
valeur_carte (Valet,!atout), valeur_carte (Neuf,Coeur), valeur_carte
(Neuf,Pique);;
- : int * int * int = 20, 0, 14
Les types somme (constructeurs constants)
Remarque : Caml reconnaît valeur_carte comme étant définie sur le
type hauteur * couleur.
Si on préfère que Caml dise que cette fonction est du type carte -> int
Les types somme
(constructeurs avec arguments)
Les types somme (constructeurs avec arguments)

Dans la section précédente, on a défini des types somme


(représentant donc des réunions disjointes de valeurs)
au moyen d’une liste finie de constructeurs constants.

Dès lors que le nombre de valeurs à considérer est si grand qu’une


telle énumération est impossible, on dispose de constructeurs avec
argument.

Un tel constructeur utilise une valeur (elle-même dans un type


existant) pour former un objet du nouveau type.
Les types somme (constructeurs avec arguments)
Un exemple classique consiste à définir un type nombre permettant
symboliquement de regrouper les entiers (valeurs de type int) et les
flottants (valeurs de type float) :

type nombre = Réel of float | Entier of int;;


Type nombre defined.
On construit ici deux valeurs du type nombre :
Réel 3.14;;
- : nombre = Réel 3.14
Entier 2010;;
- : nombre = Entier 2010
Les types somme (constructeurs avec arguments)
Dans l’exemple précédent, il y a deux constructeurs avec argument
nommés Réel et Entier.
Pour construire une valeur de type nombre, on doit :
- soit utiliser le constructeur Réel en lui passant un argument de
type float
- soit utiliser le constructeur Entier en lui passant un argument de
type int.

Le mot réservé “of” dans Réel of float (par exemple) signifie que le
constructeur Réel forme des valeurs de type nombre “à partir” des
nombres flottants.

Il est impossible d’écrire “type nombre = float | int” car il faut passer
par des constructeurs.
Les types somme (constructeurs avec arguments)
Le type nombre devient intéressant quand on y définit des
opérations (somme, produit, etc.)
Pour ajouter par exemple deux objets de type nombre (donc deux
valeurs qui ont été créées à partir des constructeurs Réel ou Entier) ,

Il faut procéder par filtrage, et utiliser les paramètres x et y (flottants


et/ou entiers) qui ont servi à construire ces valeurs :

let somme = fun


| (Réel x) (Réel y) -> Réel (x+.y)
| (Entier x) (Entier y) -> Entier (x+y)
| (Entier x) (Réel y) -> Réel (float_of_int x +. y)
| (Réel x) (Entier y) -> Réel (float_of_int y +. x);;
somme : nombre -> nombre -> nombre = <fun>
Les types somme (constructeurs avec arguments)

Voici deux exemples d’utilisation de cette fonction somme :

somme (Entier 4) (Réel 3.15);;


- : nombre = Réel 7.15

somme (Entier 4) (Entier (-1000));;


- : nombre = Entier -996
Les types somme (constructeurs avec arguments)
Il est tout à fait possible de définir un nouveau type somme à l’aide
d’un seul constructeur avec argument (avec un seul constructeur
constant, le type serait réduit à un singleton...)
On peut par exemple le type rationnel de la manière suivante :
type rationnel = Q of int*int;;
Type rationnel defined.
Q(3,4);;
- : rationnel = Q (3, 4)

La conversion d’un rationnel en flottant est évidente :


let float_of_Q (Q(x,y)) = (float_of_int x) /. (float_of_int y);;
float_of_Q : rationnel -> float = <fun>
float_of_Q (Q(395,1024));;
- : float = 0.3857421875
Les types enregistrement
(ou types produit)
Les types enregistrement (ou types produit)

Un type enregistrement est similaire à un produit cartésien à ceci près


que les composantes, au lieu d’être identifiées par leur position dans
le produit, sont identifiées par une étiquette (on dit aussi un label).

Pour définir un élément d’un tel type (on parle plutôt dans ce cas d’un
enregistrement), il suffit de préciser (dans un ordre quelconque) les
valeurs (dans ce cas on dit plutôt les champs) lié(e)s à ces étiquettes.
Les types enregistrement (ou types produit)
Prenons un exemple très simple. On veut créer une base de données
de pays décrits par une capitale, une superficie (en km2), une
population et une monnaie.

On peut par exemple écrire :


type pays = {cap:string; sup:int; pop:int; money: string};;
Type pays defined.

On voit que la définition d’un type enregistrement prend la forme


suivante :
type nom du type = { etiq1 : typ1 ; etiq2 : typ2 ; · · · ; etiqn : typn }
où etiq1, . . . , etiqn sont les étiquettes, associées respectivement à
des champs de type typ1, . . . , typn.
Les types enregistrement (ou types produit)
On peut par exemple définir le Chili.
let chili = {cap = "Santiago"; sup = 756950; pop = 16454143; money =
"Peso"};;
chili : pays = {cap = "Santiago"; sup = 756950; pop = 16454143;
money = "Peso"}

On suit donc de très près la définition du type pays, à la différence


qu’un objet du type enregistrement est défini avec la syntaxe :

let nom objet du type = { etiq1= value1 ; etiq2= value2 ; · · · ; etiqn= valuen }
Les types enregistrement (ou types produit)
On définit maintenant le Pérou. On voit qu’on peut entrer les champs
de l’enregistrement dans un ordre quelconque.
En fait nous avons ici inversé l’ordre initial de la population et de la
superficie (comme les deux champs sont de type int, une telle
interversion aurait des conséquences néfastes dans une
représentation où le type pays serait le produit cartésien
string*int*int*string).

let pérou = {cap = "Lima"; pop = 29180899; sup = 1285220; money =


"Nuevo Sol"};;
pérou : pays = {cap = "Lima"; sup = 1285220; pop = 29180899; money =
"Nuevo Sol"}

Sur l’exemple précédent, on voit que Caml remet lui-même les champs
dans l’ordre correspondant à la définition initiale du type pays (et qui
correspond à la représentation en mémoire des objets de ce type).
Les types enregistrement (ou types produit)
Bien sûr, on se heurte à une erreur si on oublie de spécifier un champ
de saisie.

let équateur = {cap = "Quito"; pop = 13927650; money = "Us Dollar"};;


Toplevel input:
> let ´equateur = {cap="Quito"; pop=13927650; money="Us Dollar"};;
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The label sup is not defined in this record.
Les types enregistrement (ou types produit)
Pour accéder en lecture à la valeur d’un champ d’un objet de type
enregistrement on utilise l’opérateur “ . ” infixé entre le nom de
l’objet et celui de l’étiquette.
Par exemple si on veut extraire la valeur du champ étiqueté
population de l’objet chili on écrit chili.pop

Voici par exemple une fonction qui calcule la densité de population


d’un pays (puisqu’on utilise la division entière / du type int, le résultat
est l’approximation entière par défaut)

let densité x = x.pop/ x.sup;;


densité : pays -> int = <fun> (∗ on voit que Caml a reconnu le type pays -> int ∗)
densité chili, densité pérou;;
- : int * int = 21, 22 (∗ le Pérou est donc à peine plus dense que le Chili ∗)
Les types enregistrement (ou types produit)

Voici comment on peut également définir la fonction densité.


let densité {pop = p; sup = s} = p/s;;
densité : pays -> int = <fun>
densité chili;;
- : int = 21
Les types enregistrement (ou types produit)
Avec notre définition du type pays, on ne peut pas modifier (par
exemple) la population de l’objet pérou.
Pour que cela soit possible (et il faut donc le prévoir dès la création du
type), il faut ajouter le mot-clef mutable devant la ou les étiquettes du
ou des champs qu’on veut pouvoir modifier

Voici par exemple comment on peut réécrire le type pays.


type pays = {cap:string; sup:int; mutable pop:int; money: string};;
Type pays defined.

Il faut alors redéfinir les objets du ce type.


let chili = {cap = "Santiago"; sup = 756950; pop = 16454143; money =
"Peso"};;
chili : pays = {cap = "Santiago"; sup = 756950; pop = 16454143;
money = "Peso"}
Les types enregistrement (ou types produit)

Voici comment on pourrait modifier la population du Chili (on retrouve


le même opérateur <- qui permet de modifier un caractère d’une
chaîne, on un élément d’un vecteur) :
chili.pop <- 16460000;;
- : unit = () (∗ bien noter que le résultat est de type unit ∗)
chili;;
- : pays = {cap = "Santiago"; sup = 756950; pop = 16460000; money =
"Peso"}

Vous aimerez peut-être aussi