Gestion des exceptions dans Haskell

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

  • essayer (Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • capture (Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a)
  • 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 quelle fonction?
  2. Comment utiliser cette fonction avec un exemple simple?
  3. Où est la différence entre la prise et la poignée? Ils ont presque la même signature seulement avec un ordre différent.

Je vais essayer d'écrire mes essais et j'espère que vous pouvez 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 définir une sortie d'erreur personnalisée?

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

Catch/try

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

69
demandé sur hammar 2011-05-15 19:38:21

4 réponses

Quand dois-je utiliser quelle fonction?

Voici la recommandation du contrôle.Documentation d'Exception:

  • si vous souhaitez effectuer un nettoyage dans le cas où une exception est déclenchée, utilisez finally, bracket ou onException.
  • pour récupérer après une exception et faire autre chose, le meilleur choix est d'utiliser l'une des familles try.
  • ... sauf si vous récupérez à partir d'une exception asynchrone, auquel cas utilisez catch ou catchJust.

Try:: Exception e = > IO a - > IO (soit e a)

try prend une action IO à exécuter et renvoie une Either. Si le calcul réussit, le résultat est donné enveloppé dans un constructeur Right. (Pensez à droit, par opposition à tort). Si l'action a lancé une exception du type spécifié , elle est renvoyée dans un constructeur Left. Si l'exception était Pas du type approprié, elle continue à se propager dans la pile. Spécifier SomeException comme type interceptera toutes les exceptions, qui peuvent ou peut-être pas une bonne idée.

Notez que si vous voulez attraper une exception d'un calcul pur, vous devrez utiliser evaluate pour forcer l'évaluation dans le 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 a

catch est similaire à try. Il tente d'abord d'spécifié IO 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 y a un important différence. Lorsque vous utilisez catch, votre gestionnaire ne peut pas être interrompu par une exception asynchrone (c'est-à-dire lancée depuis un autre thread via throwTo). Les tentatives d'élever une exception asynchrone bloqueront jusqu'à ce que votre gestionnaire ait fini de s'exécuter.

Notez qu'il y a un catch différent dans le prélude, donc vous voudrez peut-être faire import Prelude hiding (catch).

Poignée :: Exception e = > e -> IO a) -> IO a -> IO a

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

TryJust, catchJust et handleJust

Notez que try, catch et {[24] } attrapera toutes les exceptions du type spécifié / inféré. tryJust et amis vous permettent de spécifier une fonction de sélection qui filtre les exceptions que vous souhaitez spécifiquement gérer. Par exemple, toutes les erreurs arithmétiques sont de type ArithException. Si vous ne voulez attraper DivideByZero, vous pouvez 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 des exceptions ne peut se produire que dans du code impur (c'est-à-dire la monade IO). Si vous avez besoin de gérer les erreurs dans le code pur, vous devriez chercher à renvoyer des valeurs en utilisant Maybe ou Either à la place (ou un autre type de données algébrique). Ceci est souvent préférable car il est plus explicite afin que vous sachiez toujours ce qui peut arriver où. Monades comme Control.Monad.Error rend ce type de gestion des erreurs plus facile à travailler avec.


Voir aussi:

119
répondu hammar 2011-05-15 20:11:04

Edward Z. Yang a un article sur la gestion des exceptions dans haskell: 8 façons de signaler les erreurs dans Haskell revisited .

4
répondu haroldcarr 2015-03-10 19:41:10

Re: question 3: attraper et manipuler sont les meme (constaté par le biais de hoogle). Le choix de l'utilisation dépendra généralement de la longueur de chaque argument. Si l'action est plus courte, utilisez catch et vice versa. Exemple de poignée Simple de la documentation:

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

En outre, vous pouvez éventuellement curry la fonction handle pour faire un gestionnaire personnalisé, que vous pouvez ensuite passer, par exemple. (adapté 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
1
répondu Boris 2011-05-15 16:39:10

Je vois qu'une chose qui vous agace aussi (votre deuxième question) Est l'écriture de :: IO (Either SomeException ()) et cela m'a agacé aussi.

J'ai changé du 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"

À ceci:

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'extension ScopedTypeVariables GHC mais je pense qu'esthétiquement ça vaut le coup.

1
répondu Emmanuel Touzery 2014-01-04 09:30:15