Menu1 Menu2 Menu3 Menu4 Menu5 Menu6

Sommaire :
Qu'est-ce qu'une exception ?
Comment créer une exception ?
Récupérer les exceptions

I) Qu'est-ce qu'une exception ?
Les mathématiques ont montré l'importance de préciser lors de l'introduction d'une fonction son ensemble de définition, c'est-à-dire l'ensemble des objets admettant une image par cette fonction. Par exemple, la fonction inverse est définie sur l'ensemble des nombres complexes non nuls. Que se passe-t-il en Caml ? Essayez let f n = 1000/n;;. Vous définissez une fonction dont le type est « int -> int ». Cela laisse-t-il entendre que pour n'importe quel entier n, f n donnera un entier ? Pas tout à fait. Ce typage veut en fait dire que l'argument de votre fonction doit être un entier, mais pas que n'importe quel entier peut être argument de cette fonction. D'ailleurs, essayez f 0;;. Caml ne vous donne pas de résultat entier (et il a raison) : il vous répond par « Exception : Division_by_zero ». C'est une exception !
Une exception est une valeur particulière, qui interrompt le programme lorsqu'on tente de faire une opération interdite. Il existe dans OCaml de nombreuses exceptions prédéfinies. Parmi elles, on a vu Division_by_zero. Voici un autre exemple :
let queue l = match l with
|t::q -> q;;

On a vu que lorsqu'on utilise un filtrage, il n'est pas obligatoire de filtrer tous les cas possibles, si on sait qu'on n'utilisera que les cas prévus (ici les listes non vides). Ainsi, Caml a accepté votre fonction tout en fournissant un avertissement. Si vous demandez la queue de [2;5] ou ["fg";"ggjhghj";"jghgjg";"";"hjk"] avec cette fonction, pas de problème. Mais essayez queue []. Là, Caml ne va pas trouver dans votre filtrage de cas correspondant à cette liste. Il provoque une nouvelle exception : Match_failure ("",1,14). (Les entiers précisés dans la parenthèse précisent la nature du problème, je n'insiste pas sur ce point).
Il existe aussi des exceptions spécifiques au module Graphics par exemple. Bref, Caml dispose de tout un arsenal d'exceptions pour parer à des possibilités diverses. Comme je le disais, les exceptions sont pour lui des valeurs. Elles ont donc comme toute valeur leur type, qui est noté « exn » (Essayez Division_by_zero;; : Caml vous fait la réponse habituelle, avec comme type « exn »). Mais un grand avantage est qu'en plus de toutes ces exceptions existantes, vous pouvez créer les votres !

II) Comment créer une exception ?
Il existe principalement trois méthodes. La première est d'utiliser failwith. Cette fonction doit être suivie d'une chaîne de caractères. L'appel à cette fonction déclenche alors une exception dont la valeur est « Failure "blabla" ». Par exemple, redéfinissons queue :
let queue l = match l with
|t::q -> q
|[] -> failwith "La liste vide n'a pas de queue !!!";;

Cette fois, Caml ne donne aucun avertissement, car vous avez prévu tous les cas. Et justement dans le cas que vous ne voulez pas, le résultat sera l'exception « Failure "La liste vide n'a pas de queue !!!" ». Le calcul sera interrompu et l'utilisateur du programme verra ce message. Pour ma part, j'utilise fréquemment cette méthode, justement car je peux donner comme argument à failwith un message du genre « erreur : contactez le webmaster ».
La deuxième méthode est de créer une exception désignée par un mot clé de votre choix. Vous obtiendrez une exception du même genre que Division_by_zero, mais avec le nom de votre choix. Pour utiliser une telle exception, vous devrez d'abord la définir : tapez par exemple exception Liste_vide;; (le nom de votre exception DOIT commencer par une majuscule). Dès lors, vous pourrez utiliser cette exception en utilisant la fonction raise :
let queue2 l = match l with
|t::q -> q
|[] -> raise Liste_vide;;

Cette fois, si vous appliquez queue2 à la liste vide, vous obtiendrez l'exception Liste_vide, comme vous aviez obtenu l'exception Division_by_zero.
La troisième méthode est très semblable : vous allez pouvoir définir plusieurs exceptions dépendant d'un paramètre. Par exemple, au lieu de l'exception Liste_vide, je souhaite définir l'exception Mauvaise_longueur_liste avec la longueur de la liste en argument. L'utilité par rapport à l'exception précédente est que je pourrai réutiliser cette définition pour une fonction dont l'argument ne doit pas être une liste de longueur 67. Pour faire cela, la syntaxe ressemble beaucoup : « exception Nom of type;; » où type est le type du paramètre de l'exception (ici, je vais prendre un entier, mais je peux mettre ce que je veux). Puis l'exception pourra être utilisée toujours avec raise. Ici, je vais procéder ainsi :
exception Mauvaise_longueur_liste of int;;
let queue3 l = match l with
|t::q -> q
|[] -> raise (Mauvaise_longueur_liste 0);;

Un appel à la liste vide rendra alors l'exception (Mauvaise_longueur_liste 0), et je peux utiliser dans mes autres fonctions des exceptions (Mauvaise_longueur_liste n) avec n'importe quelle valeur entière de n. Une telle définition peut par exemple permettre de numéroter vos exceptions personnelles pour identifier directement l'erreur quand une exception est déclenchée.
[parenthèse à lire uniquement si vous avez bien compris ce qui précède. Si vous regardez en profondeur, la première méthode que j'ai citée revient à un cas particulier de la troisième : en tapant exception Failure2 of string;; let failwith2 s = raise (Failure2 s);;, vous retrouvez exactement failwith et Failure.]

III) Récupérer les exceptions
Maintenant, vous savez créer des exceptions. Je veux créer une fonction qui donne le second élément d'une liste d'entiers, et qui rend 0 si cet élément n'existe pas. Je vais donc prendre la tête de la queue. Que pensez-vous de : let second_element l = match (queue l) with
|t::q -> t (* si la queue de l est de la forme tête::queue, je rend tête *)
|[] -> 0 (* sinon, la queue de l est vide donc l n'a pas de deuxième élément *);;

Si vous essayez avec une liste non vide, tout marche. Mais avec la liste vide, rappelez-vous : « queue l » va renvoyer une exception. Cela va donc interrompre l'exécution de second_élément et rendre cette erreur sans se soucier de la suite. Or ce qu'on voudrait, c'est avoir 0. Autrement dit, on veut rendre un résultat même si une exception est déclenchée : c'est ce qu'on appel rattraper une exception. Pour cela, on dispose de la syntaxe suivante : « try (1) with |(2) -> (3) |(4) -> (5) ... ;; » où (1) est l'expression normale qu'on doit calculer, (2) et (4) sont des exceptions (donc des valeurs du type exn), (3) la valeur à renvoyer si (2) est déclenchée, (5) la valeur à renvoyer si (4) est déclenchée et ... contient autant qu'on veut d'autres structures du type « |(2) -> (3) » (on retrouve une syntaxe semblable aux filtrages par motifs). Caml agit alors ainsi : il essaye d'évaluer (1). S'il trouve une valeur, il la rend sans se soucier du reste. Sinon, s'il se retrouve avec une exception, il regarde si elle correspond au cas (2). Si elle correspond, il donne la valeur (3). Sinon, il regarde si l'exception correspond à (4) et donne alors la valeur (5). Sinon, il regarde encore l'exception suivante, etc... Et si aucune des exceptions proposées ne lui convient, il rendra l'exception provoquée par (1). Par exemple, pour notre fameuse fonction :
let resultat_si_aucune_exception l = match (queue l) with
|t::q -> t
|[] -> 0;;
let second_element l = try (resultat_si_aucune_exception l) with
|Failure "La liste vide n'a pas de queue !!!" -> 0;;

Comme vous le voyez, si vous faites un appel avec la liste vide, vous obtiendrez la valeur 0 et aucune exception ne sera déclenchée !!!

> Haut de la page