Probleme – Les exceptions ne servent pas qu'aux erreurs
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 2 — Le langage OCaml
Énoncé
Le programme officiel prévient : « on veille à ne pas laisser penser que les exceptions servent uniquement à gérer des erreurs ». On cherche une valeur dans une matrice.
- Écrire une version qui parcourt toute la matrice avec deux boucles
foret mémorise le résultat dans une référence. - Écrire une version qui sort dès la valeur trouvée, par une exception.
- Compter les cases lues par chacune, et comparer aussi leurs résultats.
Corrigé
1 et 2. Les deux versions, instrumentées par un compteur pour rendre les accès visibles.
let visitees = ref 0
let lire m i j = incr visitees; m.(i).(j)
(* cherche_complet m v : Some (i, j) tel que m.(i).(j) = v, ou None. *)
let cherche_complet m v =
let trouve = ref None in
for i = 0 to Array.length m - 1 do
for j = 0 to Array.length m.(i) - 1 do
if lire m i j = v then trouve := Some (i, j)
done
done;
!trouve
exception Trouve of int * int
(* cherche_exception m v : Some (i, j) pour la PREMIERE occurrence de v
dans l'ordre lexicographique des indices, ou None. *)
let cherche_exception m v =
try
for i = 0 to Array.length m - 1 do
for j = 0 to Array.length m.(i) - 1 do
if lire m i j = v then raise (Trouve (i, j))
done
done;
None
with Trouve (i, j) -> Some (i, j)
3. La mesure, sur une matrice contenant deux fois la valeur cherchée, en et en :
complet -> Some (80, 2), 10 000 cases lues
exception -> Some ( 3, 7), 308 cases lues
Deux différences, et la seconde est la plus importante.
Le coût. cases contre : la sortie anticipée s'arrête à la case . Quand la valeur est absente, les deux lisent les cases — mesuré également. La sortie anticipée ne change pas la complexité dans le pire cas, elle change tout dans le cas favorable.
Le résultat. Les deux fonctions ne calculent pas la même chose. La version complète écrase sa référence à chaque occurrence : elle rend donc la dernière. La version à exception rend la première. Une seule ligne de spécification sépare deux fonctions dont on aurait juré qu'elles étaient équivalentes, et aucun test sur une matrice à occurrence unique ne les distinguerait.
Une troisième version, avec deux boucles while et un drapeau !trouve = None dans chaque condition, lit également cases : l'exception ne fait pas gagner un seul accès sur elle. Ce qu'elle fait gagner, c'est la sûreté de l'écriture. Le drapeau doit être testé dans les deux conditions de boucle, et l'oublier dans l'extérieure ne se voit pas :
drapeau complet : Some (3,7), 308 cases lues, 4 tours exterieurs
drapeau partiel : Some (3,7), 308 cases lues, 100 tours exterieurs
Même résultat, même nombre de lectures — la boucle intérieure s'arrête aussitôt —, et pourtant quatre-vingt-seize tours à vide qu'aucun test ne révèle. Sur un parcours récursif d'arbre (chapitre chap:arbres), il faudrait de plus propager l'information de retour à chaque niveau.
L'exception est ici une structure de contrôle, pas un signalement d'erreur : elle exprime « j'ai fini, inutile de continuer », et elle le dit d'un seul endroit. C'est exactement l'usage que le programme officiel demande de ne pas laisser ignorer.
Une exception traverse silencieusement toutes les fonctions intermédiaires. Le try doit donc rester proche du raise, et l'exception être spécifique — ici Trouve, déclarée pour cet usage. Un try ... with _ -> ... attrape aussi bien un Invalid_argument venu d'un accès hors bornes, et transforme un bogue en résultat plausible. On n'attrape jamais ce qu'on n'a pas levé soi-même.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.