Académique Documents
Professionnel Documents
Culture Documents
SOMMAIRE
INTRODUCTION
1 + 1 ;;
- : int = 2
1 + 1 ;;
- : int = 2
• 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 :
Tant qu’on ne redéfinit par la constante a, elle reste liée à la valeur 3595890 :
a/10 ;;
- : int = 359589
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
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”.
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”
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 !! ∗)
• 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.
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).
make_string 7 `a`;;
-: string = "aaaaaaa“
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`
&& (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,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.
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.
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.
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 :
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)
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;;
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)
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 :
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)
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;;
Par exemple (s’il y a plusieurs variables, on voit qu’un seul appel à function ne
convient pas) :
(* 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
| 0 -> 1
| _ -> n*(fact (n-1));;
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).
On peut bien sûr réaliser une liaison locale par un “let name =ref expr in ...”.
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
• Ne pas confondre les délimiteurs “[|” et “|]” avec “[” et “]” qui définissent les
listes (voir plus loin)
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)
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)
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;; (*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)
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 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 ;;
- : 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
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 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 ∗)
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).
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.
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) ,
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.
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).
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.