IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Haskell : Gestion des exceptions
Par stendhal666

Le , par stendhal666

0PARTAGES

Attention, ceci n’est pas la saison 8 de 24H. Vous y trouverez en revanche une réflexion palpitante sur les modèles de gestion des exceptions à disposition du programmeur, dans l’esprit du billet précédent. Je commencerai par le résumer en quelques phrases car je crains que l’aridité du titre et la longueur du développement n’en aient découragé certains. J’en recommande néanmoins la lecture, pour son intérêt intrinsèque et le dur travail que m’a demandé sa traduction.

Précédemment, dans func’programming…
La théorie des catégories permet de formuler, de façon consciente et explicite, les concepts, restrictions et à-peu-près qui façonnent un langage de programmation. En effet, les « fonctions » qui y sont utilisées, loin d’être de simples fonctions mathématiques, y sont des outils de puissance variable qui autorisent –ou non, et de façons différentes- la gestion des erreurs, l’interaction avec le monde extérieur ou la référence à des paramètres globaux : elles sont donc mieux décrites comme composant des « catégories », au sens de la théorie des catégories.

Les flèches de Kleisli sont l’abstraction de la théorie des catégories la plus immédiatement applicable aux langages de programmation : alors qu’une fonction mathématique associe un ensemble A à un ensemble B, la flèche de Kleisli associe un ensemble A à un ensemble K(B), où K ajoute un supplément d’âme à B : la gestion des erreurs, l’interaction avec le monde extérieur, etc. Reste à définir un nouvel opérateur de composition, car, si a -> b et b -> c se composent naturellement, a -> K(b) et b -> K(c) ne le peuvent pas.

Les catégories utilisées dans la plupart des langages de programmation sont fixées par le standard et ne peuvent pas être modifiées, obligeant à respecter des conventions parfois à la limite de la lisibilité. Un langage comme Haskell, qui permet de manipuler les abstractions de la théorie des catégories, lève cette restriction et permet de créer les catégories les plus adaptées à la tâche envisagée.

Jack Bauer
Jack Bauer doit retrouver les codes qui empêcheront une bombe nucléaire d’exposer au cœur de Los Angeles. Pour cela, il doit mettre la main sur un mercenaire immoral, qui lui donnera l’adresse d’un terroriste, qui détient quelque part une pirate informatique très jolie, qui a caché dans la consigne d’une gare les codes en question. A première vue :

if ( prendreCodes(cachetteDeLaPirate(adresseDuTerroriste(repaireDuMercenaire()))) == CODE ) print « Ouf !» else print « Boom ! »

Néanmoins, il y a une bonne chance que Jack Bauer, à cause de sa fille, échoue dans l’une de ces tâches…

MacGyver : scotch, ficelle et boîte d’allumettes
C’est l’option du langage C. On n’a pas d’outil spécial pour la gestion des exceptions, alors on fait avec ce qu’on a sous la main. Première solution : renvoyer une valeur conventionnelle pour l’échec, comme NULL, -1, etc. Je ne m’étendrai pas trop sur les défauts de cette méthode, je pense que tout le monde est au courant. Seconde solution : la valeur de retour est un boléen qui indique s’il y a échec, et le véritable retour est placé dans un paramètre à modifier :

Code C : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char* repaire ; 
if (repaireDuMercenaire(repaire)) { 
  char* adresse ; 
  if (adresseDuTerroriste(repaire, adresse) { 
     char* cachette ; 
     if (cachetteDeLaPirate(adresse, cachette)) { 
        long codes ; 
        if (prendreCodes(cachette, &codes) { 
           if (codes == CODES) printf(« OUF ! ») ; else printf(« Boom ») ; 
        } 
        else printf (« Boom ! ») ; 
      } 
      else printf (« Boom ! ») ; 
    } 
     else printf (« Boom ! ») ; 
  } 
   else printf (« Boom ! ») ; 
} 
else printf (« Boom ! ») ; // ce else est en trop, mais il faut compter pour s'en apercevoir...

C’est une solution qui marche, mais… Disons que de temps en temps, plutôt que d’improviser une montgolfière miniature, on préférerait prendre l’ascenseur.

James Bond : une voiture qui vole
La deuxième solution est d’ajouter des features au langage. On a une voiture, on voudrait voler, on achète une voiture qui vole. On a des fonctions C, on voudrait gérer les exceptions de façon structurée, on passe à C++. Chacune des fonctions contient un garde-fou : si l’action échoue, on balance une exception et on court-circuite l’exécution du programme jusqu’à la clause de gestion des exceptions (le catch).

Cette solution est largement préférable à la première. La solution évidente :

if ( prendreCodes(cachetteDeLaPirate(adresseDuTerroriste(repaireDuMercenaire()))) == CODE )

n’est toujours pas possible, puisqu’il faut au moins un try quelque part. Mais un code comme :

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
try { 
  long codes = prendreCodes(cachetteDeLaPirate(adresseDuTerroriste(repaireDuMercenaire()))) 
  std : :cout << (codes == CODE ? « ouf ! » : « boom ! ») ; 
} 
catch () { 
  std : :cout << « Aaargh ! Kim Bauer a encore frappé ! » 
}

est tout de même lisible et robuste.

L’inconvénient est qu’il a fallu réécrire tout le programme en C++ et que, entre temps, Los Angeles est devenue un ville fantôme. Plus sérieusement, la gestion des exceptions a son côté obscur (voir ce billet, par exemple) et ses difficultés (voir celui-là). C’est un de ces domaines où l’expérience ne s’achète pas.

Haskell : la monade des « peut-être »
Comme j’introduis la première fois, sous l’angle technique, l’utilisation de la théorie des catégories, j’utiliserai la façon la plus simple de gérer les exceptions –mais un mécanisme plus puissant demanderait des modifications mineures.

Adoptons l’approche développée dans le billet précédent : la première étape est de décrire ce que serait le domaine d’arrivée d’une fonction susceptible d’échec ; la réponse est qu’il s’agit de l’ensemble des éléments de succès et, a minima (c’est notre approche aujourd’hui), d’un élément d’échec. Créons un type pour représenter ce domaine d’arrivée :

data PeutEtre a = Juste a | Rien --à noter qu’un constructeur de type, pas une valeur, représente l’échec

par exemple, la fonction retournant l’adresse du terroriste aurait pour type :

adresseDuTerroriste :: String -> PeutEtre String

Elle prend en argument le repaire du mercenaire, et si Jack torture suffisamment cruellement le mercenaire, elle retourne Juste adresse, sinon Rien. Néanmoins, la fonction cachetteDeLaPirate a également pour type :

cachetteDeLaPirate :: String -> PeutEtre String


On ne peut donc pas lui donner directement la valeur de retour de adresseDuTerroriste…

Composition et identité
Vient la deuxième étape décrite dans le billet précédent : indiquer comment se font la composition et l’identité :

compose : : PeutEtre a -> (a -> PeutEtre b) -> PeutEtre b


Rien de trop compliqué : si on reçoit Rien, le résultat est forcément Rien ; si on reçoit Juste x, alors le résultat est f x :

Code Haskell : Sélectionner tout
1
2
compose Rien _ = Rien 
compose (Juste x) f = f x

Voilà pour la composition. L’identité est la fonction qui associe à x dans A, x dans K(A). Elle est encore plus simple à définir.

Code Haskell : Sélectionner tout
1
2
identity : : a -> PeutEtre a 
identity x = Juste x

En effet, Rien n’existe pas dans A ; donc tout élément de A est nécessairement un succès, un Juste x.

Monade, classe et syntaxe
Les monades sont en Haskell une classe de type, ou typeclass (pour un rappel de ces notions, voir ce billet). Elle est définie de la façon suivante :

Code Haskell : Sélectionner tout
1
2
3
class Monad m where 
  return :: a -> m a --identity 
  (>>=) : : m a -> (a -> m b) -> m b  --compose

Notre monade PeutEtre pourrait donc être définie de la façon suivante :

Code Haskell : Sélectionner tout
1
2
3
instance Monad PeutEtre where 
	return = identity 
	(>>=) = compose

Nous pouvons donc exprimer la quête de Jack de la façon suivante :

repaireDuMercenaire >>= adresseDuTerroriste >>= cachetteDeLaPirate … --oh shit !

C’est un bon début, mais il reste à comparer les codes obtenus avec les codes corrects. Or le comparateur (==) prend deux arguments de type identique, et j’ai un PeutEtre Code d’un côté, un Code de l’autre. Il faut donc que je puisse lier le code contenu dans le PeutEtre à une variable, afin de le comparer au code correct. Nouvel essai :

repaireDuMercenaire >>= adresseDuTerroriste >>= cachetteDeLaPirate >>= \code -> … --oh shit !

Mais il faut encore que le résultat de la comparaison soit un PeutEtre, si je veux respecter le type de compose… Heureusement nous avons identity pour cela, ouf !

repaireDuMercenaire >>= adresseDuTerroriste >>= cachetteDeLaPirate >>= \code -> return (code == CODE)

Pour récapituler :

Code Haskell : Sélectionner tout
1
2
3
4
5
queSestIlPasse (Juste True) = « Ouf ! » 
queSestIlPasse (Juste False) = « Boom ! » 
queSestIlPasse Rien = « Aaargh ! Kim Bauer a encore frappé » 
  
resumeDeLEpisode = printLn $ queSestIlPasse (repaireDuMercenaire >>=  adresseDuTerroriste >>= cachetteDeLaPirate >>= \code -> return (code == CODE))

Conclusion :
Voici une première monade, toute simple. Elle existe déjà dans la bibliothèque standard d’Haskell, sous le nom de Maybe. L’important, plus que l’implémentation (on l’a vu, rien de très difficile), est d’avoir pu créer un système de gestion des exceptions en quelques lignes de code, sans support spécifique du langage.

Exercices :
- définir une nouvelle monade, pour un type :
data Try a = Succes a | Exception String
qui renvoie soit Succes a si tout s’est bien passé, soit Exception messageDErreur
- une liste est-elle une monade ? Justifiez
- si vous ne l’avez pas déjà fait, lisez le billet précédent

Une erreur dans cette actualité ? Signalez-nous-la !