Option Chap0
Option Chap0
Option Informatique
Anthony Lick
Lycée Janson de Sailly
Ocaml : un langage fonctionnel
Introduction au langage OCaml
OCaml
Le langage que l’on va utilisé en Option
Informatique est OCaml (qui vient
de “Objective Caml”, car s’est une
extension du langage Caml permettant
de faire de la programmation
orientée objet).
Il a été créé en 1996 par l’équipe
de Xavier Leroy, chercheur INRIA à
l’université de Paris-Diderot.
Introduction au langage OCaml
OCaml vs Python
On va commencer par voir les bases de la programmation en
OCaml, en le comparant à Python (utilisé en Informatique
Commune).
OCaml : un langage fonctionnel
Styles de programmation
On distingue deux principaux styles de programmation :
• Les langages impératifs.
,→ ex : C, C++, Java, Python, . . .
• Les les langages fonctionnels.
,→ ex : Haskell, Scala, OCaml, . . .
Programmation impérative vs Programmation fonctionnelle
Programmation impérative
La programmation impérative est un paradigme de
programmation qui décrit les opérations en séquences
d’instructions exécutées par l’ordinateur pour modifier l’état
du programme.
Un état représente l’ensemble des variables d’un programme.
L’exécution d’un programme consiste, à partir d’un état
initial, à exécuter une séquence finie de commandes
d’affectation modifiant l’état courant.
Les boucles (for et while) sont à disposition pour permettre
la répétition d’instructions et les structures conditionnelles (if,
then, else) pour permettre l’exécution conditionnelle
d’instructions.
Programmation impérative vs Programmation fonctionnelle
Programmation impérative
La quasi-totalité des processeurs qui équipent les ordinateurs
sont de nature impérative : ils sont faits pour exécuter du code
écrit sous forme d’opcodes (pour operation codes), qui sont
des instructions élémentaires exécutables par le processeur.
L’ensemble des opcodes disponibles forme le langage machine
spécifique au processeur et à son architecture.
Programmation impérative vs Programmation fonctionnelle
Programmation impérative
L’état du programme à un instant donné est défini par le
contenu de la mémoire centrale à cet instant, et le programme
lui-même est écrit en style impératif en langage machine, ou
le plus souvent dans une traduction lisible par les humains du
langage machine, dénommée assembleur.
Les langages impératifs suivent cette nature, tout en
permettant des opérations plus complexes (il n’y a pas de
boucles en assembleur) : c’est pour cela qu’ils sont les plus
répandus.
Programmation impérative vs Programmation fonctionnelle
Programmation fonctionnelle
La programmation fonctionnelle est un paradigme de
programmation qui considère le calcul comme l’évaluation de
fonctions mathématiques.
Ainsi, un programme est une fonction au sens mathématique,
l’exécution d’un programme étant l’évaluation d’une fonction.
Le programmeur écrivant un programme dans le paradigme
fonctionnel va écrire des fonctions, simples à la base, puis de
plus en plus complexes en les composant : une fonction déjà
écrite sert de “boite noire” dans une autre.
Programmation impérative vs Programmation fonctionnelle
Programmation fonctionnelle
Dans un langage fonctionnel, il n’y a pas de différence de nature
entre une fonction et un objet simple (un entier ou un
flottant, par exemple). Une fonction est une expression comme
une autre et à ce titre pourra elle-même être l’argument ou le
résultat d’une autre fonction.
En programmation fonctionnelle, on ne dispose pas
d’instructions permettant de modifier l’état du programme, il
n’y a donc pas de boucles (qui changeraient l’état) : celles-ci
sont remplacées par l’usage de la récursivité (capacité d’une
fonction à s’appeler elle même).
Programmation impérative vs Programmation fonctionnelle
Programmation fonctionnelle
En pratique, les langages fonctionnels sont écrits dans un
langage de plus bas niveau (plus proche du langage machine),
et masquent à l’utilisateur la nature impérative du processeur
qui va exécuter le programme.
OCaml est un langage fonctionnel, écrit en C (comme Python
d’ailleurs) qui est un langage impératif de bas niveau.
Exemple
Exemple
Il y a deux manières de calculer la factorielle : avec un produit
ou par récurrence.
n
(
Y 1 si n = 0
n! = i n! =
i=1
n × (n − 1)! sinon
Impératif vs Fonctionnel
• La première formule correspond à une vision impérative :
on peut calculer ce produit avec une boucle for.
• La deuxième correspond à une vision fonctionnelle : on
peut définir une fonction qui va s’appeler elle-même, et
calculer son résultat récursivement.
Exemple
Exemple
En Python, il sera plus naturel d’implémenter la factorielle de
manière impérative, mais on peut aussi définir des fonctions
récursives.
n
(
Y 1 si n = 0
n! = i n! =
i=1
n × (n − 1)! sinon
Python vs OCaml
• Python est un langage impératif permettant l’usage de
la récursivité.
• OCaml est un langage fonctionnel permettant l’usage de
la programmation impérative.
,→ Le style naturel pour implémenter la factorielle est plutôt
le premier pour Python, et le deuxième pour OCaml, mais
il est possible de faire l’inverse.
Un IDE pour OCaml : OCaml-Top
1 # 4+1 ;;
2 - : int = 5
Exemple
Commençons par le calcul simple ci-dessus.
• Le prompt (#) au début de la première ligne est l’invite
de l’interpréteur.
• Le reste de cette ligne a été écrit au clavier.
• La deuxième ligne est le résultat du calcul.
Un langage fortement typé
1 # 4+1 ;;
2 - : int = 5
Premières remarques
Comme on le voit sur cet exemple :
• un calcul en Caml termine par deux points-virgules ;;
• Caml procède (avant même l’évaluation) par une analyse
de types : il sait avant même de le calculer que le résultat
de l’opération est un entier (int).
Un langage fortement typé
unit
Le type unit en Caml correspond au type None en Python.
Il n’y a qu’une constante égale à ce type-là, dont l’utilité
apparaîtra plus tard.
On peut la construire à l’aide de deux parenthèses.
1 # () ;;
2 - : unit = ()
Les entiers
int
Le type int correspond aux entiers.
Sur une implémentation 64 bits, ceux-ci sont restreints à la
plage de valeurs J−262 , 263 − 1K.
Les opérateurs sur les entiers sont +, *, -, / (division entière),
et mod (modulo).
1 # 2*16 ;;
2 - : int = 32
3 # 58 mod 14 ;;
4 - : int = 2
Les flottants
float
Le type float correspond aux flottants.
Ce sont essentiellement les mêmes qu’en Python.
Les opérations sont les mêmes que pour les entiers (sauf mod
qui n’a pas d’équivalent), mais suivis d’un point qui les
différencie de leurs équivalents sur les entiers.
Les fonctions usuelles (cos, sin, exp, atan, . . .) sont définies
sur les flottants, ainsi que l’opérateur d’exponentiation (**).
1 # 2.0 *. 8.9 ;;
2 - : float = 17.8
3 # 2.0 ** 8.9 ;;
4 - : float = 477.712891666845508
5 # cos 5. ;;
6 - : float = 0.283662185463226246
Les flottants
1 # 2.0 * 3.0 ;;
2 Characters 0-3:
3 2.0 * 3.0 ;;
4 ^^^
5 Error: This expression has type float but an expression was expected of type int
6 # 2 *. 3.0 ;;
7 Characters 0-1:
8 2 *. 3.0 ;;
9 ^
10 Error: This expression has type int but an expression was expected of type float
11 # 2 ** 5 ;;
12 Characters 0-1:
13 2 ** 5 ;;
14 ^
15 Error: This expression has type int but an expression was expected of type float
bool
Semblables aux booléens de Python, le type bool de Caml
n’a que deux constantes : true et false (sans majuscules).
Les opérateurs logiques sont && (et logique), || (ou logique),
et not (non logique).
En terme de priorité, not est prioritaire sur && qui l’est sur ||.
Inégalités
Pour tester des inégalités, la syntaxe est la même qu’en
Python : <, >, <=, et >=.
Attention, en Caml il faut que les deux valeurs que l’on
compare soit du même type.
1 # 1 < 2 ;;
2 - : bool = true
3 # 5.0 >= 12.0 ;;
4 - : bool = false
5 # 2.5 < 1 ;;
6 Characters 6-7:
7 2.5 < 1 ;;
8 ^
9 Error: This expression has type int but an expression was expected of type float
Les booléens
Égalités
• Tester la différence se fait avec le symbole <>.
• Tester l’égalité se fait avec un seul symbole =.
1 # 1 = 1 ;;
2 - : bool = true
3 # 1 <> 1 ;;
4 - : bool = false
Attention
En Caml, les opérateurs == et != existent, mais testent
l’égalité physique (c’est-à-dire si les deux valeurs sont sto-
ckées au même endroit dans la mémoire).
Attention à ne pas confondre avec la syntaxe Python !
1 # 2.0 == 2.0 ;;
2 - : bool = false
Les booléens
Évaluation paresseuse
Comme en Python, les opérateurs && et || sont paresseux : si
la partie gauche suffit à déterminer le résultat de l’opération,
la partie droite n’est pas évaluée.
Notez que ceci n’empêche pas Caml d’exiger naturellement la
cohérence du type. false && 1 n’a pas de sens et provoquera
une erreur.
Déclarations de variables et
références
Déclaration globale
Déclaration
Pour déclarer une variable, on utilise let.
La déclaration permet de déclarer une variable globale, qui ne
change pas à moins qu’un autre déclaration globale ne l’écrase.
1 # let x = 44 ;;
2 val x : int = 44
Constantes
Parler de variable ici est abusif : on déclare plutôt une constante.
En effet, on effectue simplement une liaison entre un nom (celui
de la variable) et une valeur, mais on ne peut pas la modifier.
Déclaration locale
Déclaration locale
Le même mot clé let combiné avec in sert à effectuer une
déclaration locale.
Si la variable utilisée était déjà associée à une valeur, celle-ci est
temporairement oubliée, mais retrouvée à la fin de l’exécution.
Exemple
√
4
√
4 2 √
4 3
Voici un moyen de calculer 5+ 5 + 5 .
1 # let x = 5.**0.25 in x +. x *. x +. x ** 3. ;;
2 - : float = 7.07511828360311945
3 # x ;;
4 - : int = 44
Déclaration locale
Remarque
Si le nom de la variable n’était pas lié à une valeur avant la
déclaration locale, il ne l’est toujours pas après.
Déclaration simultanée
On peut déclarer simultanément deux variables avec le mot clé
and.
Cette déclaration peut bien sûr être locale.
1 # let x = 0 and y = 1 ;;
2 val x : int = 0
3 val y : int = 1
4
5 # let x=0 and y=1 in x+y ;;
6 - : int = 1
Déclaration simultanée
Remarque
Il ne faut pas confondre déclaration locale et simultanée.
Si la variable z n’est pas définie avant l’exécution du code
ci-dessus, on obtient une erreur.
Références
Variables
Les variables de Caml ne sont donc pas des variables au sens
traditionnel des langages de programmation, puisqu’il est
impossible de modifier leur valeur.
Il est pourtant souvent nécessaire d’utiliser dans les programmes
des variables modifiables comme en Python.
Références
Références
En Caml, on utilise pour cela une référence modifiable vers
une valeur, c’est-à-dire une case mémoire dans laquelle on peut
lire et écrire le contenu.
Pour créer une référence, on applique le constructeur ref au
contenu initial de la case mémoire.
1 # let x = ref 0 ;;
2 val x : int ref = contents = 0
Références
Références
La variable x est liée à une valeur (de type int ref), qui est
une référence pointant vers 0 à la création.
Pour lire le contenu d’une référence, on utilise l’opérateur de
déférencement !, qui signifie “contenu de”.
1 # !x ;;
2 - : int = 0
Références
Adresse mémoire
De même qu’avec une déclaration “globale” avec let, la
liaison entre la variable et la case mémoire pointée est défini-
tive jusqu’à ce qu’une nouvelle déclaration écrase cette liaison.
Plus précisément, la valeur de x est ici l’adresse de la case
mémoire (modifiable) qui contient la valeur, et cette adresse
est une constante.
Pour modifier le contenu d’une référence on utilise l’opérateur
d’affectation :=.
Références
Exemple
Par exemple, x := !x + 1 incrémente le contenu de la case
mémoire pointée par x, de manière similaire à x+=1 ou x=x+1
en Python.
1 # x := !x + 1 ;;
2 - : unit = ()
3 # !x ;;
4 - : int = 1
Références
Programmation impérative
Les références sont liées à la possibilité de faire de la
programmation impérative en Caml : on les utilisera donc
souvent dans des boucles.
Le type de la valeur pointée par une référence est fixé à la
création : on a vu précédemment que x était de type int ref.
On peut de même créer des bool ref, float ref, . . . et des
références vers des types plus complexes.
Références
Remarque
Les opérations x := !x + 1 et x:= !x - 1 étant d’usage
très courant, elles ont un raccourci.
1 # !x ;;
2 - : int = 1
3 # incr x ;
4 - : unit = ()
5 # !x
6 - : int = 2
7 # decr x ; !x ;;
8 - : int = 1
Quelques types plus complexes
Les tuples
Tuples
Les tuples (n-uplets en français) sont similaires aux tuples de
Python : ils permettent de construire des séquences de valeurs
de type possiblement différents.
Le type d’un tuple (t1 , t2 , . . . , tn ) est le produit cartésien des
types de ses éléments.
Remarque
Les parenthèses sont facultatives.
Les tuples
En pratique
En pratique, les tuples seront souvent utilisés comme valeur de
retour de fonctions.
On peut récupérer dans des variables les composantes d’un
tuple.
Couples
Les fonctions fst et snd permettent de récupérer la première
et la deuxième composante d’un couple.
Attention, ça ne marche qu’avec des couples (tuples de taille 2).
array
En Caml, les tableaux (array en anglais) sont très similaires
aux listes de Python, à deux restrictions près :
• La taille du tableau est fixée à la création, et ne peut pas
changer.
Il n’y a donc pas d’équivalent aux méthodes pop et append
pour les tableaux Caml.
• le type des éléments d’un tableau doit être le même pour
tous les éléments.
On aura donc par exemple des int array, des bool array,
des (int * bool) array, . . .
Les tableaux
Syntaxe
Pour déclarer un tableau, les éléments sont séparés par des
points-virgules, et placés entre [| |].
1 # [|5; 0; 7|] ;;
2 - : int array = [|5; 0; 7|]
Les tableaux
Indices
De même qu’en Python, si n est la taille du tableau, les
éléments sont indexés de 0 à n − 1.
Pour l’accès et la modification des éléments, si t est un tableau
de taille n et i un indice (entre 0 et n − 1), on utilise :
• t.(i) pour récupérer la valeur stockée à l’indice i, et
• t.(i) <- x pour la changer en x.
Remarque
L’expression t.(0) <- 2 a pour type unit, mais elle a un
effet de bord : la modification de la première case du tableau.
Pour parcourir des tableaux, on utilise fréquemment des
références et des boucles : on fait donc de la programma-
tion impérative.
Fonctions utiles sur les tableaux
Commande Description
[|0; 1; 7; 8; 9|] construit un tableau par donnée explicite
des éléments.
t.(i) renvoie le i-ème élément de t (0 ≤ i < n),
avec n la taille de la liste.
t.(i) <- x remplace le i-ème élément de t par x.
[Link] t renvoie la longueur de t.
[Link] n x construit un tableau de longueur n
contenant des x.
À retenir
Les commandes ci-dessus sont à retenir absolument.
Fonctions utiles sur les tableaux
Commande Description
[Link] n f construit un tableau contenant les f (i)
pour 0 ≤ i < n.
[Link] t renvoie une copie de t.
[Link] t i k renvoie un tableau de longueur k, égal à
la portion de t qui démarre à l’indice i.
[Link] t1 t2 concatène deux tableaux (ne pas
confondre avec Python).
[Link] l concatène une liste de tableaux.
Array.make_matrix n m x construit une matrice de taille (n, m)
contenant des x.
Les éléments sont accessibles par
t.(i).(j).
Fonctions utiles sur les tableaux
Commande Description
[Link] f t crée un tableaux dont les éléments sont les f (x)
pour x dans t.
[Link] f t applique f sur chaque élément de t (f est de
type ’a -> unit).
[Link] f t trie le tableau t en place, avec la fonction de
comparaison f.
f x y renvoie un entier, nul si les éléments sont
égaux, > 0 si x > y, et < 0 sinon.
Les chaînes de caractères
char et str
Contrairement à Python, Caml fait la distinction entre un
caractère (type char) et une chaîne de caractère (type
string).
Syntaxe
• Les caractères sont encadrés par des apostrophes.
• Les chaînes sont encadrées par des guillemets.
Les chaînes de caractères
1 # let s="abc" ;;
2 val s : string = "abc"
3 # s.[0] ;;
4 - : char = 'a'
Sémantique
Les chaînes sont très semblables à des char array, à ceci près
qu’elles sont immuables, comme en Python.
,→ On ne peut pas modifier la valeur d’un s.[i].
Les chaînes de caractères
Fonctions usuelles
Bien que très semblables à des char array, les chaînes de
caractères ont leur propre syntaxe, et leurs propres fonctions
associées. En voici quelques unes.
Commande Description
'a' le caractère a.
"abc" la chaîne de caractère abc.
s.[i] le i-ème caractère de s.
[Link] s longueur de la chaîne s.
[Link] n c crée la chaîne ccc · · · c de longueur n.
[Link] s i k extrait la sous-chaîne de s de taille k
commençant à l’indice i.
s1^s2 renvoie la concaténation de s1 et s2.
Les chaînes de caractères
Remarque
La longueur d’une chaîne est directement liée au nombre
d’octets utilisés pour représenter un caractère.
Attention aux caractères accentués, qui ne sont pas ASCII.
Ce sont des chaînes de taille 2, et pas des caractères.
1 # [Link] "é" ;;
2 - : int = 2
3 # let c = "é" ;;
4 val c : string = "\195\169"
5 # let c = 'é' ;;
6 Characters 9-10:
7 let c = 'é' ;;
8 ^
9 Warning 3: deprecated: ISO-Latin1 characters in identifiers
10 Error: Syntax error
Fonctions
Les fonctions
Fonctions
En Caml, une fonction est une valeur, qui a donc un type.
Le type d’une fonction sera de la forme :
type du paramètre → type du résultat
1 # int_of_float ;;
2 - : float -> int = <fun>
3 # int_of_float (3.5) ;;
4 - : int = 3
Exemple
La fonction int_of_float convertie un entier en flottant.
Les fonctions
1 # fst ;;
2 - : 'a * 'b -> 'a = <fun>
3 # fst (true, 5.4) ;;
4 - : bool = true
Polymorphisme
La fonction fst renvoie le premier élément d’un couple.
Comme les types des composantes de ce couple peuvent être
quelconque, Caml utilise des types génériques (on dit aussi
polymorphes) pour donner le type de la fonction.
Ici, un couple générique est de type ’a * ’b, et le résultat de
fst est du type ’a car nécessairement du type de la première
composante.
Fonctions à un seul argument
Syntaxe
Pour créer une fonction a un seul argument, on utilise function.
Exemple
On crée ici la fonction qui à un entier x associe x + 1.
Caml détecte tout seul que le type est int -> int car
l’opérateur + n’est valable que sur les entiers.
Fonctions à un seul argument
Variables
Puisqu’une fonction est une valeur, on peut l’affecter à une
variable.
Appel
Pour effectuer un appel de fonction, il y a deux syntaxes
possibles :
• fonction(valeur) (comme en maths).
• fonction valeur (sans parenthèses).
1 # f(5) ;;
2 - : int = 6
3 # f 5 ;;
4 - : int = 6
5 # (function x -> x +. 1.0) 2.5 ;;
6 - : float = 3.5
Fonctions à un seul argument
Raccourcis syntaxique
Puisque la syntaxe précédente est un peu longue, il existe un
raccourcis pour déclarer une fonction a un seul argument.
En mathématiques, on écrit souvent “soit f (x) = x + 1”.
En Caml, c’est pareil (mais les parenthèses sont facultatives).
1 # let f x = x + 1 ;;
2 val f : int -> int = <fun>
Syntaxe
On déclare une fonction à un seul argument avec la syntaxe
let f x = expression ;;
Curryfication des fonctions
Mathématiques
En mathématiques, lorsqu’on considère une fonction ayant deux
arguments, on considère une application de la forme :
f : E×F →G
(x, y) 7→ f (x, y)
Informatique
En informatique (surtout en programmation fonctionnelle), il
est plus pratique de procéder autrement.
Curryfication des fonctions
Applications partielles
Notons F(E, F ) l’ensemble des applications de E dans F .
Il existe une bijection naturelle entre les ensembles F(E×F, G)
et F(E, F(F, G)) :
Exemple
Si une fonction max était écrite avec cette vision des choses,
ou pourrait facilement construire la fonction y 7→ max(20, y)
qui donne le maximum entre y et 20.
Curryfication
La plupart des fonctions Caml sont données sous cette forme,
appelée forme curryfiée.
Curryfication des fonctions
1 # max ;;
2 - : 'a -> 'a -> 'a = <fun>
3 # max 20 ;;
4 - : int -> int = <fun>
5 # max 20 58 ;;
6 - : int = 58
7 # [Link] ;; (* création d'un tableau de taille n initialisé avec l'élément x *)
8 - : int -> 'a -> 'a array = <fun>
9 # [Link] 5 0 ;;
10 - : int array = [|0; 0; 0; 0; 0|]
11 # [Link] ;; (* concaténation de tableaux *)
12 - : 'a array -> 'a array -> 'a array = <fun>
13 # [Link] [|2; 0|] ;;
14 - : int array -> int array = <fun>
Curryfication
Voici quelques exemples de fonctions Caml.
Toutes les fonctions ci-dessus sont curryfiées.
Curryfication des fonctions
1 # max ;;
2 - : 'a -> 'a -> 'a = <fun>
3 # max 20 ;;
4 - : int -> int = <fun>
5 # max 20 58 ;;
6 - : int = 58
7 # [Link] ;; (* création d'un tableau de taille n initialisé avec l'élément x *)
8 - : int -> 'a -> 'a array = <fun>
9 # [Link] 5 0 ;;
10 - : int array = [|0; 0; 0; 0; 0|]
11 # [Link] ;; (* concaténation de tableaux *)
12 - : 'a array -> 'a array -> 'a array = <fun>
13 # [Link] [|2; 0|] ;;
14 - : int array -> int array = <fun>
Curryfication
Pour une fonction curryfiée à deux arguments, f a b est équi-
valent à (f a) b : la fonction a priorité dans l’évaluation.
Curryfication des fonctions
1 # max ;;
2 - : 'a -> 'a -> 'a = <fun>
3 # max 20 ;;
4 - : int -> int = <fun>
5 # max 20 58 ;;
6 - : int = 58
7 # [Link] ;; (* création d'un tableau de taille n initialisé avec l'élément x *)
8 - : int -> 'a -> 'a array = <fun>
9 # [Link] 5 0 ;;
10 - : int array = [|0; 0; 0; 0; 0|]
11 # [Link] ;; (* concaténation de tableaux *)
12 - : 'a array -> 'a array -> 'a array = <fun>
13 # [Link] [|2; 0|] ;;
14 - : int array -> int array = <fun>
Curryfication
Inversement, une fonction de type ’a -> ’b -> ’c est en fait
une fonction curryfiée de type ’a -> (’b -> ’c).
Curryfication des fonctions
1 # max ;;
2 - : 'a -> 'a -> 'a = <fun>
3 # max 20 ;;
4 - : int -> int = <fun>
5 # max 20 58 ;;
6 - : int = 58
7 # [Link] ;; (* création d'un tableau de taille n initialisé avec l'élément x *)
8 - : int -> 'a -> 'a array = <fun>
9 # [Link] 5 0 ;;
10 - : int array = [|0; 0; 0; 0; 0|]
11 # [Link] ;; (* concaténation de tableaux *)
12 - : 'a array -> 'a array -> 'a array = <fun>
13 # [Link] [|2; 0|] ;;
14 - : int array -> int array = <fun>
Curryfication
Tout ceci se généralise naturellement à des fonctions à plus de
deux arguments.
Création de fonctions curryfiées
Exemple
L’opérateur + de Caml est un opérateur infixe (c’est-à-dire
qu’il s’utilise sous la forme a op b), mais on peut le
transformer en opérateur préfixe (c’est à dire qui s’utilise comme
une fonction curryfiée, sous la forme op a b) en l’encadrant de
parenthèses.
1 # (+) ;;
2 - : int -> int -> int = <fun>
Création de fonctions curryfiées
Exemple
Un moyen d’obtenir une fonction équivalente est d’utiliser
plusieurs fois le mot-clé function.
Syntaxe
Le mot clé fun permet de construire directement des fonctions
curryfiées à plusieurs arguments.
Syntaxe
En général, on préfère donner un nom à la fonction. Pour
cela, on peut utiliser let et se passer de fun, comme pour les
fonctions avec un seul argument.
La syntaxe générale pour déclarer une fonction curryfiée à n
arguments est : let f x1 ... xn = expression ;;
1 # let somme x y = x + y ;;
2 val somme : int -> int -> int = <fun>
3 # somme 1 6 ;;
4 - : int = 7
Création de fonctions non curryfiées
Exemple
La fonction ci-dessus teste si le couple de flottants passé en
paramètre est dans le disque unité fermé.
Une définition équivalente (ci-dessous) déconstruit le couple à
l’intérieur du corps de la fonction.
(l’usage de fst et snd était également possible)
Syntaxe
En Caml, la syntaxe d’une expression conditionnelle est de la
forme if cond then a else b, où cond est une expression
booléenne, et a et b sont des expressions de même type.
Le type de l’expression conditionnelle est ce type commun.
Le résultat de l’expression conditionnelle peut être utilisé dans
d’autres expressions.
Typage
Si a et b sont deux expressions de types différents, l’interpréteur
avertit immédiatement d’une erreur.
Exemple
Ici, l’interpréteur a détecté que la première expression (1.0)
est de type float, la deuxième doit donc également avoir ce
type.
Expressions conditionnelles
Remarque
L’absence de else est compris comme else (), où () est de
type unit.
L’expression a doit alors être de type unit.
Programmation impérative
Cette possibilité sera par exemple utilisée lorsqu’on
programmera de manière impérative : une expression
modifiant la valeur pointée par une référence a pour type
unit.
1 # let x = ref 0 ;;
2 val x : int ref = {contents = 0}
3 # if true then x := !x + 1 ;;
4 - : unit = ()
Séquence d’instructions
Séquence d’instructions
En Caml, deux expressions successives sont séparées par un
point-virgule.
Dans une suite d’expressions successives, elles doivent toutes
avoir le type unit, sauf peut-être la dernière.
Le type de l’expression totale est celui de la dernière expression.
Exemple
Dans le premier cas, print_int est exécuté car en dehors du
if ... then.
Dans le deuxième cas, on a encadré avec begin et end.
Séquence d’instructions
Remarque
On peut éviter l’usage de begin et end en utilisant des
parenthèses.
C’est plus lisible lorsqu’on écrit tout sur une ligne.
On préférera l’usage de begin et end lorsqu’on écrira des
programmes sur plusieurs lignes (avec de l’indentation).
for
La syntaxe d’une boucle for est la suivante :
for i = i1 to i2 do instructions done.
Le compteur de boucle (i ici) prend toutes les valeurs entre
i1 et i2, par pas de 1. Si i2 < i1, aucune instruction n’est
exécutée.
Les instructions, séparées par des points-virgules, ont toutes
type unit, qui est aussi le type de la boucle totale.
Il n’est pas possible de modifier le compteur de boucle.
Boucle for
Exemple
Voici une fonction, écrite dans un style impératif, qui calcule
la factorielle d’un entier.
1 let fact n =
2 let y = ref 1 in
3 for i=1 to n do
4 y:= !y * i
5 done ;
6 !y
7 ;;
1 # fact 5 ;;
2 - : int = 120
Boucle for
Pas négatif
La syntaxe for i = i1 downto i2 do instructions done
permet d’avoir un pas de −1.
Il faut donc que i1 ≥ i2 pour qu’au moins une instruction soit
exécutée.
Boucle while
while
La syntaxe d’une boucle while est très similaire :
while condition do instructions done, où condition
est une expression booléenne.
Boucle while
Exemple
L’algorithme d’Euclide suivant, de type int -> int -> int,
est écrit avec une boucle while.
Notez l’utilisation d’une variable r locale au corps de la boucle,
tandis que les références sont locales à la fonction.
1 let pgcd a b =
2 let x = ref a and y = ref b in
3 while !y > 0 do
4 let r = !x mod !y in
5 x := !y ;
6 y := r
7 done ;
8 !x
9 ;;
Filtrage
Le filtrage peut être vu comme une alternative aux if, else,
qui peuvent s’enchaîner de manière disgracieuse, mais est en
réalité beaucoup plus que ça.
Filtrage
Exemple
D’une part, il peut être utilisé pour construire une fonction
“par morceaux”. Par exemple, la fonction suivante peut être
implémentée comme ci-dessus.
sgn : Z −→ Z
0 si x = 0
x 7−→ 1 si x > 0
−1
si x < 0
Filtrage
Exemple
Une autre manière est d’utiliser un filtrage par motif (pattern
matching en anglais), avec le mot-clé function.
Pattern matching
Ceci s’étend naturellement à des fonctions de plusieurs
arguments, avec l’utilisation de fun.
Néanmoins, l’utilisation de fun et function étant assez
trompeuse, on préfère filtrer avec un match ... with.
Filtrage
Plus généralement, un filtrage permet de gérer différents
motifs possibles d’une expression.
D’autre part, il permet d’accéder aux composantes d’un type
construit (en particulier récursif), ce qui sera très utile lorsqu’on
aura vu les listes et les arbres par exemple.
Règles du filtrage par motifs
Cas successifs
Lors d’un filtrage, les cas successifs sont examinés un par un,
et le premier qui “colle” à l’expression examinée est réalisé, pas
les autres.
Exemple
Voici une réécriture du “ou exclusif” en Caml, sur les booléens.
Typage
Dans une instruction de la forme if... else..., les
expressions dans chaque bloc if et else doivent avoir le
même type.
Il en va de même des expressions qui sont résultats d’un filtrage.
Filtrage exhaustif
Un filtrage se doit d’être exhaustif, c’est-à-dire qu’il doit
pouvoir filtrer toutes les valeurs possibles de l’expression en
fonction de son type.
Règles du filtrage par motifs
Exemple
Dans la fonction suivante, qui simule le lancé d’un dé à 6 faces
et indique une action, le filtrage n’est pas exhaustif (du point
de vue de l’interpréteur Caml, qui ne voit que les types).
En effet, la fonction est acceptée, mais le filtrage non exhaustif
est signalé.
Motif joker
Avoir des filtrages non exhaustifs est disgracieux et doit être
évité. Le motif “joker” _ (underscore) permet de filtrer tous
les motifs possibles (ou une partie d’un motif).
Exemple
La dernière ligne du filtrage précédent peut avantageusement
être remplacée par un joker.
Cas inutile
De même que l’interpréteur indique si un filtrage est non
exhaustif, il indique aussi si un cas de filtrage est inutile.
Exemple
On peut déjà faire un exemple avec les couples.
Le filtrage suivant réalise des actions différentes suivant que le
couple filtré possède une composante nulle ou non.
1 match c with
2 | (0,_) -> ...
3 | (_,0) -> ...
4 | _ ->
Règles du filtrage par motifs
Liaison locale
Lorsqu’un ou plusieurs identificateurs se trouvent dans le motif
et que le filtrage réussit, une liaison locale est effectuée entre
les identificateurs et les valeurs filtrées.
Exemple
1 match c with
2 | (0,y) -> y
3 | (x,0) -> x
4 | (x,y) -> -x-y
Remarque
Notons que ce filtrage est bien exhaustif : (x,y) filtre tous
les couples possibles.
Règles du filtrage par motifs
Remarque
Dans un motif, ne peuvent figurer que des identificateurs
distincts : par exemple filtrer (x,x) n’a aucun sens.
Le mot clé when permet de relâcher un peu la rigidité du filtrage
sur motif : une fois la liaison effectuée, on peut réaliser une
comparaison de valeurs.
Exemple
Voici une réécriture de la fonction xor.
Exemple
Voici une écriture de la fonction arg donnant un argument
d’un nombre complexe identifié à un couple de flottants, avec
un filtrage (on rappelle que π = 4 arctan(1)).
Nouveaux types
En Caml, tout objet doit avoir un type.
On va maintenant voir comment construire de nouveaux types
à partir de types existants ou non.
Types enregistrement
Types enregistrement
Les types enregistrement (ou types produit) sont similaires
à des n-uplets, donc à un produit cartésien de types, à quelques
différences près :
• les composantes ont des noms (étiquettes ou champs) ;
• on peut déclarer une ou plusieurs composantes comme
modifiable (ou mutable) ;
• il n’y a pas d’ordre dans les champs d’un enregistrement.
L’utilisation d’enregistrements, en particulier modifiables, se
fait beaucoup en programmation impérative.
Types enregistrement
1 # type personne = {nom: string ; prenom: string ; age: int} ;;
2 type personne = { nom : string; prenom : string; age : int; }
3 # let a = {nom="Dupond" ; age=42 ; prenom="Jean"} ;;
4 val a : personne = {nom = "Dupond"; prenom = "Jean"; age = 42}
5 # [Link] ;;
6 - : string = "Dupond"
Exemple: filtrage
Les motifs de filtrage peuvent être des types produits.
Par exemple pour tester si une personne est majeure.
Remarque
En fait, il n’est pas nécessaire de préciser tous les
enregistrements dans un filtrage.
La fonction de droite est équivalente.
Types enregistrement
Champ modifiable
A priori, l’âge d’une personne peut changer.
On aurait pu rendre le champ age mutable.
La modification d’un champ mutable c de l’élément a en la
valeur x se fait avec a.c <- x.
Types paramétrés
On peut faire usage du polymorphisme dans les types
produits.
Exemple
Définissons nous même un type similaire aux couples, mais en
imposant des éléments homogènes.
Remarque
Bien sûr, on peut utiliser autant de types polymorphes que
nécessaire.
1 # type ('a, 'b, 'c) truc = {un: 'a ; deux: 'b ; trois: ('a * 'c) array ; quatre: bool} ;;
2 type ('a, 'b, 'c) truc = {un : 'a; deux : 'b; trois : ('a * 'c) array; quatre : bool;}
Types enregistrement
# ref ;;
1 type 'a reference_perso = { mutable contenu : 'a; } - : 'a -> 'a ref = <fun>
2 val creer_ref : 'a -> 'a reference_perso = <fun> # ( ! ) ;;
3 val acceder_ref : 'a reference_perso -> 'a = <fun> - : 'a ref -> 'a = <fun>
4 val modifier_ref : 'a reference_perso -> 'a -> unit = <fun> # ( := ) ;;
- : 'a ref -> 'a -> unit = <fun>
Exemple
À la compilation, voici les types obtenus (à gauche).
On remarque que ceux-ci sont très similaires aux opérations
semblables avec les références de Caml (à droite).
Parenthéser un opérateur infixe permet de le transformer en
opérateur préfixe, i.e. en une fonction.
Types somme
Types somme
Les types produit correspondait à des produits cartésiens, les
types somme correspondent à des unions disjointes.
De même que l’on utilise des champs pour désigner les
composantes d’un type produit, on utilise des constructeurs
pour indiquer dans quelle partie de l’union disjointe on se situe.
Les constructeurs peuvent être constants, ou d’un certain type.
Types somme
Exemple
Voici d’abord un type constitué de constructeurs constants, qui
redéfinissent les booléens.
Définition
Un tel type constitué uniquement de constructeurs constants
est dit énuméré.
Types somme
Exemple
Voici un type mélangeant entiers et flottants.
On utilise deux constructeurs aux noms explicites.
Exemple
On peut bien sûr mélanger constructeurs constants ou non.
(Oui, il manque la couleur. On pourrait facilement l’intégrer.)
Types somme et filtrage
Exemple
Voici une fonction de négation sur nos booléens fraichement
redéfinis, et une autre sur les nombres.
Types somme et filtrage
Exemple
Voici une fonction qui donne la valeur d’une carte de tarot.
On remarque que Roi et Excuse ont été filtrés ensemble.
Il est possible de procéder ainsi lorsque dans les motifs de
filtrage les identificateurs sont les mêmes (et ont les mêmes
types).
Types somme
Paramétrage
Comme pour les types produit, on peut paramétrer en faisant
usage de polymorphisme.
Exemple
Voici comment faire quelque chose qui ressemble à A ∪ B avec
A et B disjoints.
On peut bien sûr l’utiliser pour manipuler deux copies d’un
même ensemble : quelque chose comme Z ∪ Z0 , où Z0 est une
copie de Z, serait représenté par un (int * int) union.
Exceptions
Exceptions
Exceptions
Un moyen (pas explicitement au programme) pour programmer
efficacement en évitant le lourd usage d’une référence vers un
booléen est d’utiliser des exceptions.
Exceptions
1 # 1/0 ;;
2 Exception: Division_by_zero.
3 # [|5; 2|].(2) ;;
4 Exception: Invalid_argument "index out of bounds".
5 # failwith "echec" ;;
6 Exception: Failure "echec".
Exemple
Il y a 3 exceptions différentes dans cet exemple :
Division_by_zero, Invalid_argument et Failure.
Les deux dernières prennent en paramètre une chaîne de
caractères.
Exceptions
exn
Les exceptions en Caml forment un type à part entière, qui
est le type exn (abbréviation de exception, sans surprise).
1 # Division_by_zero ;;
2 - : exn = Division_by_zero
3 # Failure "truc" ;;
4 - : exn = Failure "truc"
Exceptions
Utilisation
À priori, une exception dans un programme Caml est levée
lorsqu’on tente de faire une opération interdite : diviser par
zéro, accéder à un indice d’un tableau qui n’est pas défini
(comme ci-dessus), . . ..
La levée d’une exception interrompt le déroulement du
programme, et l’exception remonte de fonctions appelées en
fonctions appelantes jusqu’au programme principal, sauf si elle
est rattrapée.
Pour rattraper une exception, il suffit d’encadrer un code
pouvant produire une exception par try ... with, et de
rattraper l’exception dans le with.
Exceptions
1 let sgn x =
2 try
3 x/abs(x)
4 with Division_by_zero -> 0
5 ;;
Exemple
La fonction ci-dessus est une implémentation Caml de :
sgn : Z −→ Z
0 si x = 0
x 7−→ 1 si x > 0
−1
si x < 0
Exceptions
1 let sgn x =
2 try
3 if x=0 then failwith "division par zero !" ;
4 x/abs(x)
5 with Failure s -> 0 (* filtrage: s est liée localement à la valeur associée à Failure *)
6 ;;
Exemple
Voici la même fonction que précédemment, avec failwith.
Exceptions
raise
Il est possible de lever nous même des exceptions (autres que
Failure avec failwith) via la fonction raise.
1 # raise ;;
2 - : exn -> 'a = <fun>
Exceptions
Exemple
Dans le code précédent, on peut donc remplacer l’instruction
failwith ... par raise (Failure ...) : c’est strictement
équivalent.
Refaisons le avec Division_by_zero qu’on lève
“manuellement”.
1 let sgn x =
2 try
3 if x=0 then raise Division_by_zero ;
4 x/abs(x)
5 with Division_by_zero -> 0
6 ;;
Exceptions
Création d’exceptions
Enfin, il est possible de créer nous même des exceptions,
simplement avec :
exception nom_nouvelle_exception ;;
Exemple
Voici un code d’une fonction permettant de chercher si un
élément se trouve dans un tableau, sans référence.
On crée une exception Trouve qu’on pourra lever pour indiquer
qu’on a trouvé l’élément.
Bien sûr, on aurait pu lever n’importe quelle exception (comme
Division_by_zero), mais c’est plus clair ainsi.
Exceptions
Exemple
La fonction ci-dessus renvoie l’indice d’un élément dans un
tableau, et −1 si l’élément n’y est pas.
Elle utilise une exception “de type entier”.