Utiliser Maybe
(ou son cousin Either
qui fonctionne fondamentalement de la même manière mais vous permet de renvoyer une valeur arbitraire à la place de Nothing
) a un but légèrement différent de celui des exceptions. En termes Java, c'est comme avoir une exception vérifiée plutôt qu'une exception d'exécution. Cela représente une chose à laquelle vous devez vous attendre , plutôt qu'une erreur à laquelle vous ne vous attendiez pas.
Donc, une fonction comme indexOf
retournera une Maybe
valeur parce que vous vous attendez à ce que l'élément ne soit pas dans la liste. Cela revient un peu à revenir null
d’une fonction, sauf d’une manière qui respecte le type, ce qui vous oblige à traiter le null
cas. Either
fonctionne de la même manière, sauf que vous pouvez renvoyer des informations associées au cas d’erreur, c’est donc plus semblable à une exception que Maybe
.
Alors, quels sont les avantages de l' approche Maybe
/ Either
? D'une part, c'est un citoyen de première classe de la langue. Comparons une fonction Either
à une autre en lançant une exception. Dans le cas exceptionnel, votre seul recours réel est une try...catch
déclaration. Pour la Either
fonction, vous pouvez utiliser les combinateurs existants pour rendre le contrôle de flux plus clair. Voici quelques exemples:
Tout d’abord, supposons que vous vouliez essayer plusieurs fonctions qui pourraient se tromper jusqu’à ce que vous n’en obteniez pas. Si vous n'en obtenez pas sans erreur, vous souhaitez renvoyer un message d'erreur spécial. C'est en fait un modèle très utile, mais ce serait une douleur horrible try...catch
. Heureusement, puisqu'il ne Either
s'agit que d'une valeur normale, vous pouvez utiliser les fonctions existantes pour rendre le code beaucoup plus clair:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Un autre exemple est d'avoir une fonction optionnelle. Supposons que vous ayez plusieurs fonctions à exécuter, y compris une qui essaie d'optimiser une requête. Si cela échoue, vous voulez que tout le reste s'exécute de toute façon. Vous pourriez écrire du code comme:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Ces deux cas sont plus clairs et plus courts que l’utilisation try..catch
et, plus important encore, plus sémantiques. Utiliser une fonction comme <|>
ou qui optional
rend vos intentions beaucoup plus claires que d’utiliser try...catch
toujours de gérer les exceptions.
Notez également que vous n'êtes pas obligé de remplir votre code de lignes telles que if a == Nothing then Nothing else ...
! Le but de traiter Maybe
et en Either
tant que monade est d'éviter cela. Vous pouvez encoder la sémantique de propagation dans la fonction bind afin que vous obteniez les contrôles null / error gratuitement. Le seul moment où vous devez vérifier est explicitement si vous voulez retourner autre chose que Nothing
donné Nothing
, et même alors il est facile: il y a un tas de fonctions de bibliothèque standard pour rendre plus agréable que de code.
Enfin, un autre avantage est que le type Maybe
/ Either
est simplement plus simple. Il n'est pas nécessaire d'étendre le langage avec des mots-clés ou des structures de contrôle supplémentaires: tout n'est que bibliothèque. S'agissant de valeurs normales, le système de types est simplifié: en Java, vous devez différencier les types (par exemple, le type de retour) des effets (par exemple, les throws
instructions) que vous n'utiliseriez pas Maybe
. Ils se comportent également comme n'importe quel autre type défini par l'utilisateur - il n'est pas nécessaire qu'un code spécial de traitement des erreurs soit intégré à la langue.
Un autre avantage réside dans le fait que Maybe
/ Either
sont des foncteurs et des monades, ce qui signifie qu’ils peuvent tirer parti des fonctions de contrôle de flux de monades existantes (qui sont assez nombreuses) et, en général, bien jouer avec d’autres monades.
Cela dit, il y a des mises en garde. D'une part, ni Maybe
ni Either
remplacer les exceptions non vérifiées. Vous voudrez un autre moyen de gérer des choses telles que la division par 0 simplement parce que ce serait difficile de voir chaque division renvoyer une Maybe
valeur.
Un autre problème est le retour de plusieurs types d’erreurs (ceci ne s’applique qu’à Either
). Avec des exceptions, vous pouvez placer différents types d'exceptions dans la même fonction. avec Either
, vous obtenez un seul type. Cela peut être surmonté avec un sous-typage ou un ADT contenant tous les différents types d'erreurs en tant que constructeurs (cette seconde approche est celle qui est habituellement utilisée dans Haskell).
Malgré tout, je préfère l' approche Maybe
/ Either
parce que je la trouve plus simple et plus flexible.
OpenFile()
peut lancerFileNotFound
ouNoPermission
ouTooManyDescriptors
etc. A Aucun ne porte pas cette information.if None return None
instructions de style.Plus important encore, une exception et une monade Maybe peuvent avoir des objectifs différents: une exception est utilisée pour signaler un problème, alors qu'une peut-être ne l'est pas.
"Infirmière, s'il y a un patient dans la chambre 5, pouvez-vous lui demander d'attendre?"
(notez le "si" - cela signifie que le médecin attend une monade peut-être)
la source
None
valeurs peuvent simplement être propagées). Votre point 5 n’est qu’un peu correct, la question est de savoir quelles situations sont sans aucun doute exceptionnelles. Il s'avère que… pas beaucoup .bind
de manière à ce que les testsNone
ne génèrent pas de surcharge syntaxique. Un exemple très simple, C # surcharge simplement lesNullable
opérateurs de manière appropriée. Aucune vérificationNone
nécessaire, même lorsque vous utilisez le type. Bien sûr, la vérification est toujours effectuée (c'est sûr), mais en coulisses et n'encombre pas votre code. La même chose s’applique d’une certaine manière à votre objection à mon objection à (5), mais je conviens que cela peut ne pas toujours s’appliquer.Maybe
comme une monade est de rendreNone
implicite la propagation . Cela signifie que si vous voulez revenirNone
donnéNone
, vous n'avez pas d'écrire un code spécial du tout. La seule fois où vous devez faire correspondre, c'est si vous voulez faire quelque chose de spécialNone
. Vous n'avez jamais besoin deif None then None
sortes de déclarations.null
vérification exactement comme ça (par exempleif Nothing then Nothing
) gratuitement carMaybe
c'est une monade. C'est encodé dans la définition de bind (>>=
) pourMaybe
.Either
) se comportant exactement commeMaybe
. Basculer entre les deux est en fait assez simple car ilMaybe
s’agit en réalité d’un cas particulierEither
. (En Haskell, vous pourriez penserMaybe
àEither ()
.)"Peut-être" ne remplace pas les exceptions. Les exceptions sont censées être utilisées dans des cas exceptionnels (par exemple, l'ouverture d'une connexion à une base de données et le serveur de base de données n'existe pas, même si elle devrait l'être). "Peut-être" sert à modéliser une situation dans laquelle vous pouvez ou non avoir une valeur valide; disons que vous obtenez une valeur d'un dictionnaire pour une clé: elle peut être là ou peut ne pas être - il n'y a rien d'exceptionnel à propos de ces résultats.
la source
J'appuie la réponse de Tikhon, mais je pense qu'il y a un point pratique très important qui manque à tout le monde:
Either
mécanisme n'est pas du tout associé aux threads.Nous constatons donc aujourd'hui que de nombreuses solutions de programmation asynchrone adoptent une variante du
Either
style de gestion des erreurs. Considérez les promesses Javascript , comme détaillé dans l’un de ces liens:Le concept de promesses vous permet d'écrire du code asynchrone comme ceci (tiré du dernier lien):
Fondamentalement, une promesse est un objet qui:
Fondamentalement, étant donné que la prise en charge des exceptions natives dans le langage ne fonctionne pas lorsque votre calcul s'effectue sur plusieurs threads, une implémentation de promesses doit fournir un mécanisme de traitement des erreurs, qui s'avère être des monades similaires à
Maybe
/Either
types de Haskell .la source
Le système de type Haskell obligera l'utilisateur à reconnaître la possibilité d'un
Nothing
, alors que les langages de programmation n'exigent souvent pas qu'une exception soit interceptée. Cela signifie que nous saurons, au moment de la compilation, que l'utilisateur a vérifié l'erreur.la source
throws NPE
à chaque signature etcatch(...) {throw ...}
à chaque corps de méthode. Mais je pense qu’il existe un marché pour les vérifiés dans le même sens que pour Maybe: la nullabilité est facultative et suivie dans le système de types.Peut-être que la monade est fondamentalement la même chose que la plupart des langages traditionnels utilisent la vérification "erreur de moyen" (sauf que cela nécessite la vérification de la nullité), et a largement les mêmes avantages et inconvénients.
la source
Maybe
nombres en écrivanta + b
sans avoir besoin de vérifierNone
, et le résultat est encore une fois une valeur facultative.Maybe
type , mais son utilisation enMaybe
tant que monade ajoute un sucre de syntaxe qui permet d’exprimer la logique null-ish de façon beaucoup plus élégante.La gestion des exceptions peut être un réel problème pour la factorisation et les tests. Je sais que python fournit une belle syntaxe "avec" qui vous permet de capturer les exceptions sans le bloc rigide "try ... catch". Mais en Java, par exemple, essayez les blocs catch sont gros, passe-partout, soit verbeux, soit extrêmement verbeux, et difficiles à dissocier. En plus de cela, Java ajoute tout le bruit autour des exceptions vérifiées et non vérifiées.
Si, au contraire, votre monade intercepte des exceptions et les considère comme une propriété de l'espace monadique (au lieu d'une anomalie de traitement), vous êtes libre de mélanger et de faire correspondre les fonctions que vous liez dans cet espace, indépendamment de ce qu'elles lancent ou capturent.
Si, mieux encore, votre monade évite les situations pouvant donner lieu à des exceptions (comme l'envoi d'un chèque nul dans Maybe), c'est encore mieux. Si ... est beaucoup, beaucoup plus facile à factoriser et à tester que d'essayer ... attraper.
D'après ce que j'ai vu, Go adopte une approche similaire en spécifiant que chaque fonction est renvoyée (réponse, erreur). C’est un peu la même chose que de "lever" la fonction dans un espace monade où le type de réponse principal est décoré avec une indication d’erreur, et élimine efficacement les exceptions.
la source