74 votes

La gestion des exceptions en Haskell

J'ai besoin d'aide pour comprendre l'utilisation des trois fonctions Haskell

  • essayez (Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • catch (Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a)
  • la poignée (Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a)

J'ai besoin de savoir plusieurs choses:

  1. Quand dois-je utiliser la fonction?
  2. Comment puis-je utiliser cette fonction avec un simple exemple?
  3. Où est la différence entre la capture et la poignée? Ils ont presque la même signature qu'avec un ordre différent.

Je vais essayer d'écrire mes essais et j'espère que vous pourrez m'aider:

essayez

J'ai un exemple comme:

x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())

J'ai deux questions:

  1. Comment puis-je personnaliser la sortie d'erreur?

  2. Que puis-je faire pour mettre toutes les erreurs à SomeException donc je ne dois écrire l' :: IO (Either SomeException())

catch/essayer

Pouvez-vous me montrer un petit exemple avec un message d'erreur personnalisé de sortie?

127voto

hammar Points 89293

Quand dois-je utiliser la fonction?

Voici la recommandation du conseil de Contrôle.La documentation des exceptions:

  • Si vous voulez faire un peu de nettoyage dans le cas où une exception est soulevée, utiliser finally, bracket ou onException.
  • Pour récupérer après une exception et de faire autre chose, le meilleur choix est d'utiliser l'un de l' try de la famille.
  • ... à moins que vous récupérez à partir asynchrone exception, dans quel cas utiliser catch ou catchJust.

essayez :: Exception e => IO a -> IO (e)

try prend un IO action à exécuter, et renvoie un Either. Si le calcul réussi, le résultat est donné enveloppé dans un Right constructeur. (Pensez à droit, par opposition à tort). Si l'action a déclenché une exception du type spécifié, il est renvoyé en Left constructeur. Si l'exception n'était pas du type approprié, il continue à se propager de la pile. La spécification SomeException que le type va attraper toutes les exceptions, qui peut ou peut ne pas être une bonne idée.

Notez que si vous voulez attraper une exception à partir d'un pur calcul, vous devrez utiliser l' evaluate de la force de l'évaluation au sein de l' try.

main = do
    result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
    case result of
        Left ex  -> putStrLn $ "Caught exception: " ++ show ex
        Right val -> putStrLn $ "The answer was: " ++ show val

catch :: Exception e => IO a - > e -> IO a) -> IO un

catch est similaire à l' try. Il tente d'abord d'spécifiés IO d'action, mais si une exception est levée, le gestionnaire est donné l'exception d'obtenir une réponse alternative.

main = catch (print $ 5 `div` 0) handler
  where
    handler :: SomeException -> IO ()
    handler ex = putStrLn $ "Caught exception: " ++ show ex

Cependant, il existe une différence importante. Lors de l'utilisation d' catch votre gestionnaire ne peut pas être interrompue par une asynchroneous exception (c'est à dire jeté partir d'un autre thread via throwTo). Essaie de soulever un asynchroneous exception bloquera jusqu'à ce que votre gestionnaire a fini de s'exécuter.

Notez qu'il y a un autre catch dans le Prélude, de sorte que vous pourriez vouloir faire import Prelude hiding (catch).

poignée :: Exception e = > e -> IO a) -> IO a -> IO un

handle est tout simplement catch avec les arguments dans l'ordre inverse. À utiliser dépend de ce qui rend votre code plus lisible, ou celui qui correspond le mieux si vous voulez utiliser l'application partielle. Ils sont par ailleurs identiques.

tryJust, catchJust et handleJust

Notez que try, catch et handle va attraper toutes les exceptions de l'/type inféré. tryJust et amis vous permettent de spécifier un sélecteur de fonction qui filtre des exceptions que vous souhaitez gérer. Par exemple, toutes les erreurs de calcul sont de type ArithException. Si vous ne voulez attraper DivideByZero, vous pouvez le faire:

main = do
    result <- tryJust selectDivByZero (evaluate $ 5 `div` 0)
    case result of
        Left what -> putStrLn $ "Division by " ++ what
        Right val -> putStrLn $ "The answer was: " ++ show val
  where
    selectDivByZero :: ArithException -> Maybe String
    selectDivByZero DivideByZero = Just "zero"
    selectDivByZero _ = Nothing

Une note sur la pureté

Notez que ce type de gestion d'exception ne peut se faire que dans impur code (c'est à dire l' IO monade). Si vous avez besoin de gérer les erreurs dans le code pur, vous devez retourner les valeurs à l'aide de Maybe ou Either au lieu (ou tout autre type de données algébrique). C'est souvent préférable car il est plus explicite, de sorte que vous savez toujours ce qui peut arriver où. Les monades comme Control.Monad.Error rend ce type d'erreur de manipulation plus facile de travailler avec.


Voir aussi:

5voto

haroldcarr Points 357

Edward Z. Yang a un article sur la manipulation d'exception en haskell:

http://blog.ezyang.com/2011/08/8-ways-to-report-errors-in-haskell-revisited/

2voto

Emmanuel Touzery Points 1677

Je vois qu'une chose aussi vous ennuie (votre deuxième question) est l'écriture de l' :: IO (Either SomeException ()) et il m'a ennuyé.

J'ai modifié le code maintenant à partir de ceci:

let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
    Left _ -> putStrLn "Error"
    Right () -> putStrLn "OK"

Pour cela:

let x = 5 `div` 0
result <- try (print x)
case result of
    Left (_ :: SomeException) -> putStrLn "Error"
    Right () -> putStrLn "OK"

Pour ce faire, vous devez utiliser l' ScopedTypeVariables GHC extension, mais je pense que esthétiquement ça en vaut la peine.

1voto

Boris Points 2390

Re: question 3: attraper et manipuler sont les mêmes (trouvé par le biais de hoogle). Le choix de l'utiliser dépend généralement de la durée de chaque argument. Si l'action est plus courte, l'utilisation de capture et vice versa. Simple poignée exemple à partir de la documentation:

do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...

Aussi, vous pouvez, en théorie au curry la poignée de la fonction d'un gestionnaire personnalisé, que vous pouvez ensuite passer autour, par exemple. (adapté à partir de la documentation):

let handler = handle (\NonTermination -> exitWith (ExitFailure 1))

Messages d'erreur personnalisés:

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler

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