On a fait des rappels sur le langage OCaml
type time = { h : int; m: int; s: float }
let midnight = { h=0; m=0; s=0.0 }
type color = RGB of int * int * int
| RGBa of int * int * int * float
| Symbolic of string
let red = Symbolic "red"
let transp_yellow = RGBa (255, 255, 0, 0.5)
Les structures de contrôles :
let rec iter_int f i j =
if i < j then begin
f i;
iter_int (i+1) j
end
let pr_int n = Printf.printf "-> %d\n" n
let () = iter_int pr_int 0 10
let pr_color c =
match c with
RGBa(r, g, b, alpha) -> Printf.printf "(%d, %d, %d, %f)" r g b a
| Symbolic name -> Printf.printf "<%s>" name
Considérons la fonction fact_alt :
let rec fact_alt n acc =
if n <= 1 then
acc
else
fact_alt (n - 1) (n * acc)
fact_alt 6 1
fact_alt 5 6
fact_alt 4 30
fact_alt 3 120
fact_alt 2 360
fact_alt 1 720
720 ← cas de base
720
720
720
720
720
La fonction fact_alt calcule son résultat « en descendant » :
fact_alt est une fonction récursive terminale.
Une fonction est récursive terminale si tous les appels récursifs sont terminaux.
Un appel récursif est terminal si c'est la dernière instruction à s'exécuter.
let rec fact_alt n acc =
if n <= 1 then
acc
else
fact_alt (n - 1) (n * acc)
let rec fact n =
if n <= 1 then
1
else
n * fact (n-1)
Le compilateur OCaml optimise les fonctions recursives terminales en les compilant comme des boucles, ce qui donne du code très efficace et qui consomme une quantité de mémoire bornée (et non pas une pile arbitrairement grande)
En OCaml, une fonction est une valeur comme une autre. On peut donc définir des fonctions locales à des expressions :
let x = 42
let x2 =
let carre n = n * n
in
carre x
La fonction carre est une fonction locale à l'expression dans laquelle elle se trouve. La notation :
let f x1 … xn =
ef
in
e
permet de définir la fonction et de ne l'utiliser que pour l'expression e. La fonction f n'est pas visible à l'extérieur.
Une utilisation courante des fonctions locales est la définition de fonctions auxiliaires à l'intérieur d'une fonction principale :
let fact m =
let rec fact_alt n acc =
if n <= 1 then
acc
else
fact_alt (n - 1) (n * acc)
in
fact_alt m 1
Ici, il est impossible d'appeler fact_alt depuis
l'extérieur (et en particulier avec de mauvais paramètres).
C'est une forme d'encapsulation.
Remarque : fact n'a pas besoin d'être récursive, il
n'y a que fact_alt qui doit être déclarée avec « let
rec ».
Les appels récursifs ne sont pas limités à une seule fonction. Il est courant de vouloir définir deux fonctions qui s'appellent l'une l'autre :
let rec pair n =
if n == 0 then
true
else
impair (n-1)
and impair n =
if n == 0 then
false
else
pair (n-1)
pair 6
impair 5
pair 4
impair 3
pair 2
impair 1
pair 0
true ← cas de base.
Remarque : ces deux fonctions sont récursives terminales !
On aura l'occasion de revoir les fonctions mutuellement récursives lorsqu'on travaillera sur des structures de données plus complexes que des entiers.
Le compilateur OCaml effectue une inférence de types :
# let x = 2 ;;
val x : int = 2
# let y = 3 ;;
val y : int = 3
# let f n = n + 1;;
val f : int -> int = <fun>
# let g x = sqrt (float x);;
val g : int -> float = <fun>
Le compilateur affecte à chaque variable de programme une variable de type.
Il pose ensuite des équations entre ces variables de types
Si des équations sont contradictoires, OCaml indique une erreur de type
let f n =
if n <= 1 then
42
else
n + 45
αn : type de n
αf : type de retour de f
αn = int (car n <= 1)
αn = int (car n + 45)
αf = int (car on renvoie 42)
αf = int (car on n + 45)
n : αn= int
f : αn -> αf = int -> int
let g n =
if n <= 1 then
42
else
"Boo!"
αn : type de n
αg : type de retour de g
αn = int (car n <= 1)
αg = int (car on renvoie 42)
αg = string (car on renvoie "Boo!")
n : int ≠ string ⇒ erreur de typage
Soit une fonction :
let f x1 … xn =
ef
Le type de f se note : t1 -> … -> tn -> tf où :
let mult_add a b c = a * b + c (* int -> int -> int -> int *)
let carte n =
if n = 1 then "As"
else if n = 11 then "Valet"
else if n = 12 then "Reine"
else if n = 13 then "Roi"
else if n > 13 then "invalide"
else string_of_int n
(* int -> string *)
let us_time h m =
let s = if h > 12 then "pm" else "am" in
let h = if h > 12 then h - 12 else h in
Printf.printf "%d:%d %s" h m s
(* int -> int -> unit *)
let to_seq j h m s =
float (j * 3600 * 24 + h * 3600 + m * 60) +. s
(* int -> int -> int -> float -> float *)
Well-typed expressions do not go wrong.
Robin Milner 1978.
Dans les langages statiquement typé (comme OCaml), les opérations sont toujours appliquées à des arguments du bon type.
Certaines classes d'erreurs sont donc impossible. Cela donne du code robuste et permet d'éviter toute un catégorie de tests.
Certaines erreurs dynamiques sont toujours possible : division par 0, stack overflow, …
L'inféference de type évite au programmeur le travail fastidieux d'écrire les types pour toutes les variables de son programme.
Comme les tests, le typage est un outil précieux pour le développement et la maintenance des programmes.
Histoire d'horreur (adaptée)
Un programmeur Javascript définit une fonction f(s)
qui prend en argument une chaîne de caractère représentant une
date s. Cette fonction est une fonction utilitaire,
utilisée par plein d'autres collaborateurs dans leur code.
Le programmeur décide changer cette fonction
en f(d,m,y) qui prend trois entiers et il oublie de
prévenir ses collègues.
Le code de l'application continue de s'exécuter. La
fonction f est appelée avec une chaîne de
caractère. Utilisée comme un nombre, elle est convertie
en NaN (nombre invalide) et ne provoque pas d'erreur,
elle renvoie juste des résultats incorrects.
En OCaml (mais aussi en Java ou en C++) : les collègues sont avertis automatiquement, le code ne compile plus ! Ils modifient leur code pour appeler f avec les bons arguments.
On revient un moment sur les fonctions fst et snd
qui permettent d'extraire la première et seconde composante d'une paire.
Elles sont définies dans la bibliothèque standard d'OCaml avec un simple
filtrage :
let fst p =
match p with
(x, _) -> x
let snd p =
match p with
(_, y) -> y
let p1 = (1, 2)
let x1 = fst p1 (* x1 : int = 1 *)
let p2 = (true, "Hello")
let y2 = snd p2 (* y2 : string = "Hello" *)
Quel est le type de fst et snd ?
Essayons de simuler le compilateur OCaml et écrivons les équations que l'on peut obtenir en typant fst :
let fst p =
match p with
(x, _) -> x
Le type final de fst est donc :
Xa*Xb->Xa
C'est exactement ce qu'OCaml nous affiche :
# fst;;
val fst : 'a * 'b -> 'a = <fun>
Une fonction est dite polymorphe si le type de ses arguments ou de ses résultats n'est pas contraint. OCaml signale de tels types par : 'a, 'b, 'c, … (on lit α, β, γ, …)
# fst;;
val fst : 'a * 'b -> 'a = <fun>
# snd;;
val snd : 'a * 'b -> 'b = <fun>
# let id x = x;;
val id : 'a -> 'a = <fun>
# let dup x = (x, x);;
val dup : 'a -> 'a * 'a = <fun>
De telles fonctions peuvent être appliquées à n'importe quelles valeurs si les types sont compatibles :
# fst (1, 2);;
- : int = 1
# snd (true, "Hello");;
- : string = "Hello"
# id 42.0
- : float = 42.0
# dup false;;
- : bool * bool = (false, false)
On peut appliquer fst à n'importe quel type paire.
Note : c'est équivalent aux variables génériques du langage Java HashMap<K, V>
Une des première chose que l'on veut faire en programmant est de gérer des
collections de valeurs (liste d'élèves, ensemble de points, lignes d'un
fichier, …).
Pour cela, les types produits (n-uplets ou nommés) ne sont pas adaptés, car :
Dans le cadre de la programmation fonctionnelle, on va s'intéresser aux collections immuables, c'est à dire non modifiables.
OCaml déifinit le type polymorphe 'a list. Il s'agit de listes dont le type des éléments est 'a. Ainsi, le type des listes d'entiers est int list, celui des listes de flottants float list, celui des listes de paires d'entiers (int * int) list, …
let l1 = [ 1; 2; 3 ]
let l2 = 0 :: l1 (* la liste [ 0; 1; 2; 3 ] *)
let l3 = 42 :: l1 (* la liste [ 42; 1; 2; 3 ] *)
Le type 'a list est en fait un type somme définit dans la bibliothèque standard d'OCaml comme :
type 'a list = [] | :: of ('a * 'a list)
En d'autres termes, c'est un type avec deux cas :
Le code OCaml :
let lst = 1 :: 4 :: 3 :: [] (* équivalent à [ 1; 4; 3; ] *)
correspond en mémoire à :
:: | 1 | ⟶ |
:: | 4 | ⟶ |
:: | 3 | ⟶ |
[] |
type 'a list = [] | :: of ('a * 'a list)
On remarque que la définition du type 'a list fait référence au type 'a list : c'est un type récursif.
Ça n'est pas spécifique à OCaml : les listes chaînées (en C, LinkedList<E> en Java, …) sont des types récursifs aussi.
⇒ La plupart des fonctions sur les listes vont être récursives
On remarque aussi que le type 'a list est un type somme avec deux constructeurs.
⇒ La plupart des fonctions sur les listes vont utiliser du filtrage.
On souhaite calculer la longueur d'une liste :
let rec longueur l =
match l with
[] -> 0
| _ :: ll -> 1 + longueur ll
(* val longueur : 'a list -> int *)
On a deux types de motifs possible sur les listes :
longueur [1; 4; 3]
longueur
:: 1 ⟶
:: 4 ⟶
:: 3 ⟶
[]
1 + longueur
:: 4 ⟶
:: 3 ⟶
[]
1 + 1 + longueur
:: 3 ⟶
[]
1 + 1 + 1 + longueur
[]
1 + 1 + 1 + 0
La fonction longueur n'est pas récursive terminale, on peut corriger ça :
let longueur lst =
let rec loop l acc =
match l with
[] -> acc
| _ :: ll -> loop ll (1+acc)
in
loop lst 0
(* val longueur : 'a list -> int *)
On souhaite écrire une fonction qui affiche une liste d'entiers dans le terminal :
let rec pr_int_list l =
match l with
[ ] -> () (* ne rien faire *)
| i :: ll ->
Printf.printf "%d\n" i;
pr_int_list ll
(* val pr_int_list : int list -> unit *)
On remarque que cette fonction est récursive terminale.
Une fonction qui renvoie le nombre de jour dans un mois :
let jours_mois = [
("janvier", 31); ("février", 28); ("mars", 31); ("avril", 30);
("mai", 31); ("juin", 30); ("juillet", 31); ("août", 31);
("septembre", 30); ("octobre", 31); ("novembre", 30); ("décembre", 31);
] (* val jours_mois : (string * int) list *)
let nb_jours m =
let rec trouve_aux m l =
match l with
[ ] -> failwith "Mois invalide"
| (n, j) :: ll ->
if m = n then (* on a trouvé le bon mois *)
j
else
trouve_aux m ll
in
trouve_aux m jours_mois
(* val nb_jours : string -> int *)
On remarque que cette fonction est récursive terminale.
Une fonction qui inverse l'ordre des éléments d'une liste :
let renverse l =
let rev renverse_aux l acc =
match l with
[] -> acc
| e :: ll -> renverse_aux ll (e :: acc)
in
renverse_aux l []
(* val renverse : 'a list -> 'a list *)
La fonction renverse_aux est récursive terminale.
renverse [ 1; 2; 10; 3 ]
renverse_aux [ 1; 2; 10; 3 ] [ ]
renverse_aux [ 2; 10; 3 ] (1 :: [ ])
renverse_aux [ 10; 3 ] (2 :: (1 :: []))
renverse_aux [ 3 ] (10 :: (2 :: (1 :: [])))
renverse_aux [ ] (3 :: (10 :: (2 :: (1 :: []))))
(3 :: (10 :: (2 :: (1 :: []))))
[ 3; 10; 2; 1 ]
Le type 'a list est paramétré par le type des éléments.
Il est donc naturel que les fonctions qui travaillent sur les listes soient
paramétrées par d'autres fonctions permettant de spécialiser leur
comportement.
On prend l'exemple de la fonction iter qui appelle une fonction f pour chaque élément d'une liste. Cette fonction ne renvoie pas de résultat.
let rec iter f l =
match l with
[ ] -> () (* ne rien faire *)
| e :: ll ->
f e;
iter f ll
(* val iter : ('a -> unit) -> 'a list -> unit *)
Quelle est l'utilité de cette fonction ?
(* Des fonctions d'affichage *)
let pr_int i = Printf.printf "%d" i
(* val pr_int : int -> unit *)
let pr_float f = Printf.printf "%f" f
(* val pr_float : float -> unit *)
let pr_int_int p = Printf.printf "<%d, %d>" (fst p) (snd p)
(* val pr_int_int : (int * int) -> unit *)
let pr_int_list l = iter pr_int l
(* val pr_int_list : int list -> unit *)
let pr_float_list l = iter pr_float l
(* val pr_float_list : float list -> unit *)
let pr_int_int_list l = iter pr_int_int l
(* val pr_int_int_list : (int * int) list -> unit *)
On va être amené a définir les opérations sur les listes en deux temps :
En pratique non ! Ils sont définis dans le module List de la bibliothèque standard. On en donne trois pour faire le TP de cette semaine :
La fonction List.assoc prend en argument une clé et une liste de paires et renvoie la valeur associée à la clé dans la liste. Si aucune clé ne correspond, la fonction lève l'exception Not_found.
let dico = [ ("A", 10); ("B"; 100); ("D", 23) ]
let b = List.assoc "B" dico (* 100 *)
let z = List.assoc "Z" dico (* provoque une erreur Not_found *)
let jours_mois = [ ("janvier", 31); ... ]
let nb_jours m = List.assoc m jours_mois
En programmation fonctionnelle lest fonctions sont des valeurs comme les autres :
(on se pose la question du point de vue de la représentation en mémoire)
:: | 1 | ⟶ |
:: | 4 | ⟶ |
:: | 3 | ⟶ |
[] |
Considérons :
# let f =
Printf.printf "Hello\n";
let u = 42 in
let g x = x + u in
g;;
Hello
val f : int -> int = <fun>
# f 1;;
- : int = 43
# u;;
Error: Unbound value u
En mémoire, les fonctions sont représentées par des clôtures, c'est à dire un couple (c, e) où :
On peut stocker de tels objets dans des structures de données simplement (c'est juste un couple, on peut le mettre dans une liste, dans un autre couple, dans une variable, …).
Lorsqu'on exécute une fonction, le processeur effectue un call (ou jal) vers l'adresse où se trouve le code. Avant ça il place e ainsi que tous les arguments sur la pile.
Lorsque le code accède à une variable non-locale, il va la chercher dans e
On a vu qu'on peut définir des fonctions partout avec la notation let f x = ... in ...
Si on reprend l'exemple de List.iter :
let pr_int x = Printf.printf "%d" x ;;
let pr_int_list l = List.iter print_int l ;;
let pr_int_list l =
let pr_int x = Printf.printf "%d" x
in
List.iter pr_int l
;;
Dans le deuxième cas, on n'a pas polué les définition avec une fonction globale.
Est-ce qu'on peut écrire ça de façon plus simple ?
Dans la définition précédente, on a défini une fonction pour ne l'utiliser qu'à un seul endroit :
let pr_int_list l =
let pr_int x = Printf.printf "%d" x
in
List.iter pr_int l
;;
On peut ré-écrire le code comme ceci :
let pr_int_list l =
List.iter (fun x -> Printf.printf "%d" x) l
;;
La notation fun x1 … xn-> e permet de définir une fonction anonyme.
Les fonctions anonymes sont particulièrement utiles lorsqu'elles sont utilisées avec des fonctions d'ordre supérieur:
let pr_int_list l =
List.iter (fun x -> Printf.printf "%d" x) l
;;
let pr_float_list l =
List.iter (fun x -> Printf.printf "%f" x) l
;;
let pr_int_int_list l =
List.iter (fun x -> Printf.printf "<%d, %d>" (fst x) (snd x)) l
;;
En OCaml, les programmes sont structurés en modules. En particulier, chaque fichier définit un module. L'ensemble des fonctions et variables d'un module est accessible en utilisant le nom du module avec une Majuscule.
Le module List définit un grand nombre de fonctions utilitaires sur les listes.
(* Ces trois fonctions sont à utiliser avec parcimonie ou pas du tout.
On privilégiera le filtrage *)
List.length : 'a list -> int (* Longueur d'une liste *)
List.hd : 'a list -> 'a (* Première valeur. Lève une exception sur la liste
vide *)
List.tl : 'a list -> 'a (* Suite de la liste. Lève une exception sur la liste
vide *)
(* *)
List.append : 'a list -> 'a list -> 'a list (* Concatène deux listes. Peut aussi
se noter l1 @ l2 *)
List.rev : 'a list -> 'a list (* Renverse une liste *)
Parmi les fonctions du module List, certaines sont des itérateurs. Elles permettent d'appliquer une fonction d'ordre supérieur à chaque élément d'une liste.
C'est le premier itérateur que l'on a vu. Il permet d'appliquer une fonction qui ne renvoie pas de résultat à tous les éléments d'une liste. On l'utilisera principalement pour faire des affichages.
List.iter : ('a -> unit) -> 'a list -> unit
# List.iter (fun x -> Printf.printf "%b\n" x) [ true; false; true ] ;;
true
false
true
- : unit = ()
Permet de filtrer, c'est à dire de renvoyer la liste de tous les éléments qui remplissent une certaine condition.
List.filter : ('a -> bool) -> 'a list -> 'a list
# List.filter (fun x -> x mod 2 = 0) [ 4; 5; 42; 1; 37; 49 ] ;;
- : int list = [ 4; 42 ]
# List.filter (fun x -> x < 25.0) [ 10.5; 2.3; 99.0 ] ;;
- : float list = [ 10.5; 2.3 ]
Le premier argument est appelé un prédicat.
Permet d'appliquer une transformation à chaque élément d'une liste et de
renvoyer la liste des images.
List.map f [v1; … ; vn] ⇝ [ (f v1); … ; (f vn)]
List.map : ('a -> 'b) -> 'a list -> 'b list
# List.map (fun x -> x * x) [ 4; 8; 3 ] ;;
- : int list = [ 16; 64; 9 ]
# List.map string_of_int [ 1; 2; 3 ] ;;
- : string list = [ "1"; "2"; "3" ]
Ça va jusqu'ici ?
Ça va se corser un peu …
On souhaite souvent « combiner » tous les éléments d'une liste. Exemple
∑i=1..n i2 = (((1 + 4) + 9) + ... )+ n2Ce type d'opération se retrouve souvent : somme, produit, min, max, … :
Min { v1, …, vn } = min(min(min (min (v1, v2), v3), …), vn)
Comment exprimer ce genre d'opérations par un opérateur générique ?
Permet d'appliquer une fonction d'agrégation aux éléments d'une liste.
List.fold_left : ('a -> 'b -> 'a ) -> 'a -> 'b list -> 'a
La fonction prend trois arguments
List.fold_left f a [ v1; …; vn ] ⇝ f (f (f (f (f a v1) v2) v3) …) vn
# List.fold_left (fun a x -> a + x) 0 [ 1; 3; 7 ] ;;
- : int = 11
# let f a x = a ^ " " ^ string_of_int x;;
val f : string -> int -> string = <fun>
# List.fold_left f "" [ 1; 3; 7 ] ;;
- : string = "1 3 7 "
Dans le code ci-dessus:
List.fold_left f "" [ 1; 3; 7 ]
f (f (f "" 1) 3) 7
f (f "1 " 3) 7
f "1 3 " 7
f "1 3 7 "
À proprement parler, le tri d'une liste n'est pas un itérateur. Il utilise cependant de l'ordre supérieur.
List.sort : ('a -> 'a -> int) -> 'a list -> 'a list
La fonction List.sort prend en argument une fonction de
comparaison.
La fonction de comparaison prend en argument deux valeur a et
b et
renvoie un nombre
négatif (a < b), nul (a = b) ou positif (a >
b).
En OCaml, la fonction prédéfinie compare a ce comportement.
compare : 'a -> 'a -> int
# List.sort compare [ 4; 8; 3 ] ;;
- : int list = [ 3; 4; 8 ]
# List.sort compare [ "C"; "A"; "B"; "AX" ] ;;
- : string list = [ "A"; "AX"; "B"; "C" ]
Considérons la fonction suivante :
let f x =
(fun y -> x + y)
;;
C'est une fonction qui :
# let f x =
(fun y -> x + y)
;;
val f : int -> int -> int = <fun>
# let g = f 3
val g : int -> int = <fun>
# g 4;;
- : int = 7
Ici, f 3 renvoie une fonction (qu'on stocke dans g). Cette fonction attend un autre argument y et renvoie 3 + y.
Si on s'intéresse aux types, quelle différence de type entre :
let f x = (fun y -> x + y) ;;
let add x y = x + y ;;
… aucune : int -> int -> int
On dit qu'une application est partielle si on applique une fonction à
un nombre d'arguments inférieur à celui attendu.
En OCaml, ce n'est pas une erreur.
Si une fonction est de type : t1 -> t2 -> … ->
tn -> s, alors on peut l'appliquer à au plus
n arguments.
Si on l'applique à k < n arguments, le résultat est une
fonction
qui attend les n-k arguments restant.
# let f x y z = x + y + z;;
val f : int -> int -> int -> int = <fun>
# let g = f 10;;
val g : int -> int -> int = <fun>
# let h = g 5;;
val h : int -> int = <fun>
# h 6;;
- : int = 21
L'application partielle est particulièrement utile, combinée à l'ordre supérieur
let pr_int_list = List.iter (Printf.printf "%d");;
(* int list -> unit *)
let incr_int_list = List.map ((+) 1);;
(* int list -> int list *)
let sum_list = List.fold_left (+) 0 ;;
(* int list -> int *)
Il est courant, en programmation de vouloir signaler une
erreur. Une des raisons pour laquelle une erreur peut se
produire est liée à la différence entre une fonction
mathématique et une fonction dans un langage de
programmation.
Considérons :
f : (x, y) ↦ x÷y définie pour x ∈ ℕ, y ∈ ℕ \ {0}
La fonction OCaml correspondante est :
let f x y = x / y (* val f : int -> int -> int *)
En mathématiques, on ne peut pas appliquer la fonction à 0. En OCaml (comme dans de nombreux langages), le seul type à notre disposition est int, auquel 0 appartient. Un programmeur peut donc écrire f 1 0 .
Dans le cas de f 1 0 le programme OCaml lève une exception :
# let f x = x / y;;
val f : int -> int -> int = <fun>
# f 1 0;;
Exception: Division_by_zero.
Ici OCaml signale une erreur indiquant qu'une division par 0 est survenue.
Une exception, si elle n'est pas gérée, interrompt le programme brutalement et affiche un message dans la console.
let () = Printf.printf "AVANT\n"
let x = 1 / 0
let () = Printf.printf "APRÈS\n"
$ ocamlc -o test test.ml
$ ./test
AVANT
Fatal error: exception Division_by_zero
$
Une exception est utilisée lorsqu'un programme veut signaler que la
poursuite du calcul est impossible.
La plupart du temps, elle est levée par une fonction en réponse à un argument
invalide.
Une exception peut aussi être déclanchée par OCaml sur du code
parfaitement valide, pour signaler une erreur système. Par exemple :
En OCaml, toutes les exceptions appartiennent au même type : exn. Ce dernier est un type somme. Pour simplifier, on peut imaginer que celui ci a été défini comme :
type exn = Division_by_zero | Failure of string | Break | Not_found | …
Il est possible de définir ses propres exceptions, ce qui correspond à ajouter un nouveau cas au type exn.
exception MonErreur
exception MonErreurAvecMessage of string
exception MauvaiseValeur of int
L'interêt des exceptions est qu'on peut les rattraper. On utilise pour cela la construction try/with qui ressembles à l'opération de filtrage match/with :
try
e
with
Exception1 -> e1
| Exception2 (s) -> e2
| …
Ici, e est évaluée en premier. Si elle ne provoque pas d'erreur, sa valeur est renvoyée. Sinon, si l'erreur provoquée est Exception1 alors e1 est renvoyée. Sinon si l'erreur Exception2 est provoquée, alors le contenu de l'exception est stocké dans s et l'expression e2 est renvoyée. Si aucune des exceptions listées ne correspond à l'erreur, cette dernière est propagée. Elle pourra alors interrompre le programme.
La fonction prédéfinie raise e permet de lever l'exception e :
exception MonErreur(x, y)
let f x y =
if x < y then
raise (MonErreur (x, y)) (* on veut que x soit plus grand ! *)
else
x - y
...
let g u v =
try
let res = f u v in
Printf.printf "Résultat : %d\n" res
with
MonErreur (x, y) -> Printf.printf "Erreur, %d est plus petit que %d" x y
Il est courant de vouloir lever une exception avec un message
d'erreur.
L'exception prédéfinie en OCaml est Failure of string. Cette
exception est tellement courante qu'il existe une fonction prédéfinie
failwith msg qui lève cette exception avec le message passé en
argument :
let aire_disque r =
if r < 0.0 then
failwith "Rayon négatif"
else
r *. r *. 3.14159
# aire_disque (-2.0);;
Exception: Failure "Rayon négatif".
#