Caml offre les structures de contrôle suivantes:
if ... then ... else
match ... with ...
;
for
et while
raise ...
et le
rattrapage d'erreurs corrélatif try ... with ...
.
Si aucune de ces structures de contrôle ne répond à votre problème, il vous faut définir vos propres structures de contrôle en définissant vos propres fonctions (récursives ou non).
Table des matières:
if ... then ... else
if ... then ...
if ... then ... else
Les tests se font de façon classique à l'aide d'une expression
if condition then expression1 else expression2
qui
renvoie expression1
ou expression2
, selon la
valeur booléenne de la condition.
Les opérateurs arithmétiques booléens sont
=
, <
, >
,
<=
, >=
, <>
,
et les opérateurs booléens sont
&&
, ||
, not
.
Par exemple:
let valeur_absolue x = if x >= 0 then x else - x;; valeur_absolue : int -> int = <fun>
Typage:
la condition doit être de type bool
, et les deux branches
doivent avoir le même type, qui est le type de toute la construction.
if ... then
Un if
sans partie else
correspondante
est complété automatiquement avec une clause else ()
. Ce
genre de construction est utile dans les
séquences.
Typage:
la condition doit être de type bool
, et les deux branches
doivent avoir le même type (unit
).
Le filtrage ou analyse de formes ou correspondance de patrons, sert à faire des tests complexes où intervient la forme de l'argument soumis à la construction.
Syntaxiquement, un filtrage se présente comme une liste de
clauses. Chaque clause est une construction de la forme
|
filtre -> expression
. Le
mécanisme de filtrage consiste à analyser la forme d'une valeur
donnée pour la comparer avec chacun des filtres (ou formes ou patrons)
de la liste des clauses. L'analyse réussit dès que la valeur peut être
considérée comme un exemple du filtre proposé par la
clause. Une valeur est un exemple d'un filtre si elle en est un cas
particulier (ou encore que le filtre est une forme générale de la
valeur ou encore que le filtre est plus général que la
valeur).
Un filtre est composé de constantes (constantes de base ou
constructeurs), de variables et d'applications de constructeurs
fonctionnels à des filtres arguments. On peut ainsi
considérer les clauses | 0 -> 1
(filtre entier
constant 0
) ou | n -> 1
(filtre réduit à
une variable n
). La seule valeur filtrée par le filtre
0
est évidemment 0
, alors que n
filtre toute valeur quelque soit sa forme. Finalement un filtre de la
forme C f
où C
est un constructeur et f
un filtre, filtre toutes les valeurs qui sont construites avec le
constructeur C
avec pour argument une valeur
v
qui est filtrée par f
.
Les filtres des clauses sont essayés successivement dans l'ordre de présentation donné dans le programme, jusqu'à ce que l'un d'entre eux réussisse. Si aucun ne réussit une erreur se produit à l'exécution (pour plus de détails).
La valeur retournée par un filtrage est toujours celle de
l'expression
correspondant à la clause qui a réussi.
La construction syntaxique de base pour déclencher un filtrage est
match expression with clauses
: elle évalue tout d'abord
expression
, obtenant une valeur dont la forme est ensuite
analysée et comparée avec chacune des clauses successives présentées
dans la partie clauses
du filtrage. Par exemple,
succ 0
valant 1
, le filtrage suivant renvoie
l'expression correspondant à la clause | 1 ->
"unité"
:
#match succ 0 with | 0 -> "nul" | 1 -> "unité" | _ -> "plusieurs";; - : string = "unité"
Remarquez la dernière clause
| _ -> "plusieurs"
qui comporte un souligné en partie
filtre: le filtre _
signifie ``n'importe quoi d'autre''
et filtre donc toute valeur. Il sert souvent comme dernier filtre pour
capturer tous les cas non envisagés plus haut dans le filtrage.
Le filtrage d'une valeur par une variable produit un autre effet:
pendant le calcul de l'expression à droite de la clause
correspondante, la variable vaut la valeur filtrée (on dit que le
filtrage a opéré une liaison de la variable à la valeur).
Par exemple, succ 1
valant 2
, le filtrage
suivant renvoie l'expression correspondant à la clause | n ->
string_of_int n
, et renvoie donc string_of_int n
avec n
valant 2
(on dit aussi que
n
est lié à 2
):
#match succ 1 with | 0 -> "nul" | 1 -> "unité" | n -> string_of_int n;; - : string = "2"
On remarque que la construction if ... then ... else ...
est un cas particulier de filtrage sur les booléens:
if cond then e1 else e2
correspondant à
match cond with | true -> e1 | _ -> e2
Typage:
dans un filtrage toutes les clauses doivent avoir le même type: les
filtres doivent tous avoir le même type et les expressions doivent
aussi avoir un type commun. Dans le cas de la construction match
e with filtrage
, le type commun aux filtres doit être celui de
e
, et le type commun aux expressions des clauses du
filtrage est celui de toute la construction.
Les filtrages doivent être dans la mesure du possible exhaustifs (tous les cas doivent être envisagés) et non redondants (il n'y a jamais deux cas qui peuvent s'appliquer). Le compilateur Caml émet généralement une alerte quand on lui présente un tel filtrage non exhaustif ou comportant des cas inutiles.
Au cas où le filtrage échoue à l'exécution parce que la valeur n'est un exemple d'aucun des filtres proposés, une exception est déclenchée qui indique généralement l'endroit du programme où l'erreur s'est produite.
Pour des raisons syntaxiques, un filtrage interne à un autre filtrage
doit toujours être parenthésé (à l'aide de parenthèses ou des mots
clés begin
et end
).
Considérez en effet le
filtrage de l'expression e2
, interne au filtrage de
l'expression e1
:
match e1 with | f1 -> match e2 with | f2 -> ... | f3 -> ... | f4 -> e2
Tel qu'écrit ici, on comprend très bien que f2
et
f3
sont des filtres du filtrage de e2
, ce qui est
parfaitement exact. En revanche, un lecteur humain interprètera sans
doute l'indentation comme signifiant que le filtre f4
appartient au filtrage englobant, celui de e1
. Pourtant
ce filtre suit naturellement f3
que nous attribuons bien
sûr au filtrage de e2
. C'est pourquoi, et malgré l'indentation
trompeuse, f4
est syntaxiquement rattaché au filtrage
interne (celui de e2
). L'intention du programmeur était
sans doute:
match e1 with | f1 -> begin match e2 with | f2 -> ... | f3 -> ... end | f4 -> e2
Pour éviter ce genre d'erreurs, ayez le réflexe de toujours parenthéser les filtrages internes à d'autres filtrages.
Lors d'un filtrage, vous pouvez évaluer une condition booléenne
quelconque (qui peut même porter sur les variables du filtre): après
le patron vous introduisez la condition par le mot-clé
when
, puis écrivez l'expression à tester. La clause
correspondante ne s'appliquera que dans le cas où la condition est
vérifiée. Sinon, le filtrage continuera comme d'habitude avec le cas
suivant. Par exemple:
(* power i j computes i ^ j, provided j is positive. *) let rec power i j = match j with | 0 when i != 0 -> 1 | 1 -> i | j when j mod 2 = 1 -> let p = power i (j / 2) in i * p * p | j when i = 0 -> invalid_arg "power" | _ -> let p = power i (j / 2) in p * p;; power : int -> int = <fun>
Un autre exemple typique consiste à tester l'égalité de deux variables d'un patron. Ce prédicat détermine si une liste comporte deux éléments successifs égaux:
let rec two_successive_equal = function | [] -> false | [_] -> false | x :: y :: l when x = y -> true | x :: l -> two_successive_equal l;; two_equal : 'a list -> bool = <fun>
Pour en savoir plus, voir aussi ici.
La séquence e1; e2
sert à signifier l'évaluation
successive des deux expressions e1
puis
e2
. La valeur de la construction est celle de
e2
. Cette construction sert évidemment à faire des
effets, par exemple des impressions:
#print_string "Hello "; print_string "World!";; Hello World!- : unit = ()
Typage: le type d'une séquence est le type de la dernière expression.
La séquence en Caml n'a pas besoin d'être parenthésée dans les clauses
de filtrage:
... -> e1; e2
est équivalent à ... -> begin
e1; e2 end
.
if ... then ... else ...
Une séquence apparaissant dans l'une des alternatives d'une
alternative, doit être parenthésée. Pour cela on utilise généralement
begin
et end
. On écrit
if cond then begin e1; e2 end else begin e3; e4 end
Corrélativement, une alternative dans une séquence ne nécessite pas de parenthésage. Ainsi
if cond1 then e1; if cond2 then e2 else begin e3; e4 end;;
signifie:
if cond1 then e1 else (); if cond2 then e2 else begin e3; e4 end;;
En revanche, si l'on ne parenthèse pas la séquence correspondant à l'une des branches d'une l'alternative, cette séquence appartient à l'expression englobante. Par exemple, dans l'expression:
if cond1 then e2 else e3; e4;;
l'expression e4
est exécutée quelquesoit la valeur de la
condition cond1
. En effet cette expression est
équivalente à:
(if cond1 then e2 else e3); e4;;
Les boucles sont de deux types, boucle for
et boucle
while
. Elles s'évaluent pour leurs effets et rendent
toujours la valeur
() ou ``rien''.
On écrit while condition do expression
done
, pour signifier l'évaluation répétitive de
expression
tant que condition
s'évalue à
vrai. En particulier, si condition
est toujours fausse,
expression
n'est jamais évaluée.
Typage:
la condition doit avoir le type bool
, mais une boucle a
toujours le type unit
.
On écrit for ident = initial to final do expression
done
, pour signifier l'évaluation répétitive de
expression
, pour des valeurs de ident
valant
successivement initial
, initial + 1
, ...,
jusqu'à final
inclus. En particulier, si
initial
est strictement supérieur à final
,
expression
n'est jamais évaluée.
Remarquez que ident
est introduit par la boucle
for
, il n'a pas besoin d'avoir été préalablement défini
ou déclaré. Remarquez aussi qu'il est impossible de modifier la valeur
de ident
pendant le déroulement de la boucle (d'ailleurs
ident
n'est pas une
référence
mutable).
#for i = 0 to 10 do print_int i; print_char ` ` done;; 0 1 2 3 4 5 6 7 8 9 10 - : unit = ()
Typage:
l'indice de boucle est du type int
de même que les
expressions initial
et final
.
La boucle a le type unit
.
Pour sortir d'une boucle de façon prématurée, il faut utiliser le
mécanisme d'exception. On déclenche l'exception prédéfinie qui est
prévue pour ce cas: l'exception Exit
(voir plus loin).
La primitive raise
sert à déclencher des exceptions
(signaler des erreurs) en cas d'impossibilité de poursuivre les
calculs. Ces exceptions devront être rattrapées par une construction
``try ... with'' englobante pour traiter l'erreur (soit en
interrompant le programme après avoir imprimé un message, soit en
poursuivant le traitement d'une autre manière). Si personne ne
rattrape l'erreur, l'échec se propage jusqu'à l'arrêt complet de
l'évaluation. Par exemple:
#print_string "Bonjour"; #raise (Failure "division par zéro"); #print_string " à tous ";; BonjourException non rattrapée: Failure "division par zéro"
Typage:
si exception
a pour type
exn
, alors raise exception
a pour type
'a
.
La construction try expression with filtrage
sert à la gestion des erreurs: on exécute expression
en
surveillant les erreurs qui peuvent se produire pendant cette
exécution. On envisage les différents cas d'erreur dans la partie
``filtrage'' du try ... with
, et dans le cas où une telle
erreur se produit on la filtre par ce filtrage, et l'on renvoie
l'expression correspondante de la clause sélectionnée. Si aucune
clause ne convient l'exception est propagée (redéclenchée).
Typage:
le typage de try expression with
filtrage
est analogue à celui de match expression with
filtrage
: les filtres du filtrage doivent tous avoir le type des valeurs
exceptionnelles, exn
, et le type commun aux expressions du
filtrage doit être celui de l'expression protégée,
expression
. Ce type est celui de toute la construction.
À l'aide du mécanisme d'exception, on implémente facilement la sortie
prématurée des boucles for
et while
(l'équivalent de l'instruction break
de C, ou
exit
du langage Ada). Pour cela, on déclenche l'exception
prédéfinie Exit
dans le corps de la boucle, et l'on pose
un surveillant d'exception à l'extérieur de la boucle.
Par exemple, nous définissons une fonction qui teste si un vecteur donné contient un élément nul: si on rencontre un 0 dans le vecteur, on sort instantanément de la boucle de parcours et l'on rend la valeur vrai, sinon la boucle se déroule jusqu'au bout, et l'on retourne faux.
let contient_un_zéro v = try for i = 0 to vect_length v - 1 do if v.(i) = 0 then raise Exit; done; false with Exit -> true;;
L'utilisateur définit ses propres exceptions avec la construction
exception nom;;
pour une exception constante, ou
exception nom of typ;;
pour une exception avec
argument. Ces exceptions sont génératives, c'est-à-dire
que deux définitions successives d'une exception de même nom, créent
deux exceptions différentes qui ne sont pas confondues par les
programmes.
Typage:
une exception constante a le type
exn
, une exception définie par exception nom of typ;;
a le type typ -> exn
.
Un certain nombre d'exception sont prédéfinies pour signaler des problèmes ou erreurs courantes:
Failure of string
: signale une erreur dans la
fonction (ou pour la cause) indiquée.
Not_found
: signale qu'une fonction de recherche a
échoué (par exemple assoc
).
Exit
: utilisée pour sortir prématurément des boucles
ou des fonctions.
Break
: interruption des calculs (généralement par une
intervention de l'utilisateur du programme).
Invalid_argument of string
: l'argument passé à la
fonction indiquée ne peut pas être traité.
Sys_error of string
: erreur du système d'exploitation
hôte de Caml (cette exception est définie dans le module sys
).
Match_failure of string * int * int
: signale un échec
du filtrage dans le fichier indiqué à l'endroit indiqué.
End_of_file
: signale une fin de fichier (cette
exception est définie dans le module io
).
Contacter l'auteur Pierre.Weis@inria.fr