40 votes

Approches de Haskell pour la gestion des erreurs

Aucun argument ici qu'il y a une variété de mécanismes en place en Haskell pour gérer les erreurs et gérer correctement les. Erreur monade, Soit, Peut-être, les exceptions, etc.

Alors, pourquoi est-ce qu'il se sent beaucoup plus simple, l'écriture exception sujettes code dans d'autres langues qu'en Haskell?

Disons que j'avais envie d'écrire un outil de ligne de commande qui traite les dossiers transmis sur la ligne de commande. J'aimerais:

  • Vérifier les noms de fichiers sont fournis
  • Vérifiez que les fichiers sont disponibles et lisibles
  • Vérifier si des fichiers ont des en-têtes
  • Créer le dossier de sortie et de vérifier les fichiers de sortie seront accessibles en écriture
  • Processus fichiers, erroring sur l'analyse des erreurs, invariant erreurs, etc.
  • Les fichiers de sortie, erroring sur erreur d'écriture, le disque est plein, etc.

Ainsi, un simple outil de traitement du fichier.

En Haskell, je serais emballage ce code dans une combinaison de monades, en utilisant Peut-être la et la et la traduction et de la propagation des erreurs que nécessaire. En fin de compte, tout cela est d'une IO monade où je suis en mesure d'afficher le statut de l'utilisateur.

Dans une autre langue, il me suffit de lever une exception et les attraper dans l'endroit approprié. Simple. Je ne passe pas beaucoup de temps cognitive dans les limbes d'essayer de démêler quelle combinaison de mécanismes dont j'ai besoin.

Suis-je simplement en approche de ce mal ou est-ce là un peu de substance à ce sentiment?

Edit: Bon, je reçois des commentaires me disant qu'il se sent juste plus difficile, mais en fait ne l'est pas. Voici donc un point douloureux. En Haskell, je fais face à des piles de monades, et si je dois gérer les erreurs, je vais ajouter une autre couche à cette monade de la pile. Je ne sais pas combien de levage et d'autres syntaxique de la litière, j'ai eu à ajouter, juste pour rendre le code compile, mais ajoute à zéro de sens sémantique. Personne ne se sent ce qui ajoute à la complexité?

32voto

C. A. McCann Points 56834

En Haskell, je serais emballage ce code dans une combinaison de monades, en utilisant Peut-être la et la et la traduction et de la propagation des erreurs que nécessaire. En fin de compte, tout cela est d'une IO monade où je suis en mesure d'afficher le statut de l'utilisateur.

Dans une autre langue, il me suffit de lever une exception et les attraper dans l'endroit approprié. Simple. Je ne passe pas beaucoup de temps cognitive dans les limbes d'essayer de démêler quelle combinaison de mécanismes dont j'ai besoin.

Je ne dirais pas que vous êtes nécessairement approche de ce mal. Plutôt, votre erreur est de penser que ces deux scénarios sont différents; ils ne sont pas.

De "il suffit de lancer et à attraper" est équivalent à imposer à l'ensemble de votre programme exactement la même structure conceptuelle comme une combinaison de Haskell méthodes de gestion d'erreurs. La combinaison exacte dépend de l'erreur des systèmes de gestion de la langue que vous êtes en la comparant à, qui souligne pourquoi Haskell semble plus compliqué: Il vous permet de mélanger et assortir erreur de manipulation de structures fondées sur le besoin, plutôt que de vous donner un implicite, one-size-fits-plus solution.

Donc, si vous avez besoin d'un style particulier de la gestion d'erreur, vous l'utilisez; et que vous l'utilisez uniquement pour le code qui en a besoin. Code qui n'en a pas besoin, pour des raisons de ni génération, ni de la manipulation de la pertinente sortes d'erreurs--est marqué comme tel, ce qui signifie que vous pouvez utiliser ce code sans se soucier de ce genre d'erreur en cours de création.


Sur le sujet syntaxique de la maladresse, c'est une drôle d'objet. En théorie, il devrait être indolore, mais:

  • Haskell a été dictée par la recherche de la langue pendant un certain temps, et, à ses débuts, beaucoup de choses étaient encore en pleine évolution et utile idiomes n'avait pas été popularisés encore, si vieux code flottant autour est susceptible d'être un mauvais modèle
  • Certaines bibliothèques ne sont pas aussi souples qu'ils pourraient l'être dans la façon dont les erreurs sont traitées, soit en raison de fossilisation de l'ancien code comme ci-dessus, ou tout simplement manque de polonais
  • Je ne suis pas au courant de tout les guides sur la meilleure façon de structure du nouveau code de gestion d'erreur, de sorte que les nouveaux arrivants sont laissés à leur propre sort

Je pense que les chances sont que vous êtes "faire le mal" en quelque sorte, et pourrait éviter la plupart de ce désordre syntaxique, mais que ce n'est probablement pas raisonnable de s'attendre à ce que vous (ou la moyenne des Haskell programmeur) pour trouver les meilleures approches sur leur propre.

Aussi loin que monade transformateur piles aller, je pense que l'approche standard est-à - newtype de l'ensemble de la pile pour votre application, et d'en tirer ou de mettre en œuvre des instances pour les classes de type (par exemple, MonadError), puis utilisez le type de la classe des fonctions qui ne sont pas généralement besoin d' lifting. Monadique fonctions que vous écrivez pour le cœur de votre application doivent utiliser le newtyped pile, afin de ne pas avoir besoin de levage, soit. À propos de la seule basse-sémantique-sens chose que vous ne pouvez pas éviter est - liftIO, je pense.

Traiter avec de grandes piles de transformateurs peuvent être des maux de tête, mais seulement lorsqu'il y a beaucoup de calques imbriqués des différents transformateurs (empiler des couches alternées de StateT et ErrorT avec un ContT jeté dans le milieu, puis juste essayer de me dire ce que votre code sera effectivement le faire). C'est rarement ce que vous voulez vraiment, bien.


Edit: en tant Que mineur, addendum, je veux attirer l'attention sur une remarque plus générale qui m'est apparu lors de l'écriture de quelques commentaires.

Comme je l'ai dit et @sclv démontré joliment, de corriger les erreurs de manipulation vraiment est si compliqué que ça. Tout ce que vous pouvez faire est de l'aléatoire que la complexité autour de, pas à l'éliminer, parce que peu importe ce que vous êtes la réalisation de plusieurs opérations qui peuvent produire des erreurs de façon indépendante et que votre programme doit gérer toutes les combinaisons possibles de toute façon, même si cette "manipulation" est tout simplement de tomber et mourir.

Cela dit, Haskell vraiment ne diffèrent intrinsèque de la plupart des langues sur un point: de manière générale, de gestion des erreurs est à la fois explicite et premier de la classe, ce qui signifie que tout est à l'air libre et peut être manipulé librement. Le revers de la médaille c'est une perte de implicite de gestion des erreurs, ce qui signifie que même si tout ce que vous voulez, c'est pour imprimer un message d'erreur et meurent, vous devez le faire explicitement. Donc, en fait, faire d'erreur de manipulation est plus facile en Haskell, en raison de la première classe des abstractions, mais en ignorant les erreurs est plus difficile. Cependant, ce genre de "toutes les mains abandonner le navire" erreur non la manipulation est presque jamais correct dans toute sorte de monde réel, la production, l'utilisation, c'est pourquoi il semble que la maladresse obtient écarté.

Ainsi, même s'il est vrai que les choses sont plus compliquées au premier abord, lorsque vous avez besoin de traiter avec des erreurs explicitement, la chose importante est de se rappeler que c'est tout là est à lui. Une fois que vous apprendre à utiliser la bonne gestion des erreurs d'abstractions, de la complexité assez bien atteint un plateau et n'a pas vraiment d'obtenir beaucoup plus difficile qu'un programme s'étend; et plus vous l'utilisez ces abstractions les plus naturelles qu'ils deviennent.

26voto

sclv Points 25335

Regardons certains de ce que vous voulez faire:

Vérifier les noms de fichiers sont fournis

Et s'ils ne le sont pas? Juste de quitter, non?

Vérifiez que les fichiers sont disponibles et lisibles

Et si certains ne le sont pas? Processus les autres, de lever une exception quand vous frappez une mauvaise, avertir sur les mauvais et de gérer les bons? Quittez avant de faire quoi que ce soit?

Vérifier si des fichiers ont des en-têtes

Et si ils ne le font pas? Même question -- skip les mauvaises abandon précoce, mettent en garde sur les mauvaises, etc...

Processus fichiers, erroring sur l'analyse des erreurs, invariant erreurs, etc.

Encore une fois, et faire ce qu', ignorer les mauvaises lignes, ignorer les mauvais fichiers, abandonner, abandonner et de restauration, imprimer une mise en garde, d'impression configurable niveaux de mises en garde?

Le point est qu'il y a de choix et d'options disponibles. Pour faire ce que vous voulez d'une manière qui reflète l'impératif façon, vous n'avez pas besoin de tout peut-être ou eithers de la monade des piles à tous. Tous vous avez besoin est de lancer et attraper les exceptions dans les IO.

Et si vous ne voulez pas utiliser des exceptions partout, et obtenir un diplôme de maîtrise, vous pouvez toujours le faire sans monade des piles. Par exemple, si vous souhaitez traiter les fichiers que vous le pouvez et obtenir des résultats, et les erreurs de retour sur les fichiers que vous ne pouvez pas, alors Eithers travail super, il suffit d'écrire une fonction de FilePath -> IO (Either String Result). Ensuite, mapM que sur votre liste de fichiers d'entrée. Ensuite, partitionEithers la liste, puis mapM une fonction d' Result -> IO (Maybe String) sur les résultats, et catMaybe les chaînes d'erreur. Maintenant, vous pouvez mapM print <$> (inputErrors ++ outputErrors) pour afficher toutes les erreurs qui sont venus dans les deux phases.

Ou, vous savez, vous pouvez faire quelque chose d'autre aussi. En tout cas, à l'aide de Maybe et Either dans la monade des piles a sa place. Mais pour le type d'erreur de manipulation cas, c'est plus pratique de travailler avec eux directement et explicitement, et très puissant aussi. Il faut juste un peu de s'habituer à la grande variété de fonctions pour leur rendre la manipulation plus pratique.

7voto

alternative Points 7053

Quelle est la différence entre l'évaluation à l' Either e a et le pattern matching, vs un try et catch, outre le fait qu'il se propage avec des exceptions (Et si vous utilisez de la monade, vous pouvez simuler ce)

Gardez à l'esprit que la plupart du temps le monadique utilisation de quelque chose qui est (à mon avis) laid, sauf si vous avez une quantité importante de l'utilisation de fonctions qui peuvent échouer.

Si vous avez seulement un possible échec, theres rien de mal avec

func x = case tryEval x of
             Left e -> Left e
             Right val -> Right $ val + 1

func x = (+1) <$> trvEval x

C'est juste une fonctionnel manière de représenter la même chose.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X