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:
- Quand dois-je utiliser quelle fonction?
- Comment utiliser cette fonction avec un exemple simple?
- 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:
Comment puis-je définir une sortie d'erreur personnalisée?
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?
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
ouonException
. - 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
oucatchJust
.
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:
Edward Z. Yang a un article sur la gestion des exceptions dans haskell: 8 façons de signaler les erreurs dans Haskell revisited .
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
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.