Gestion des exceptions dans Haskell

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

  • try ( Control.Exception.try :: Exception e => IO a -> IO (Either ea) )
  • catch ( Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a )
  • handle ( Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a )

J’ai besoin de savoir plusieurs choses:

  1. Quand est-ce que j’utilise quelle fonction?
  2. Comment utiliser cette fonction avec un exemple simple?
  3. Où est la différence entre prise et poignée? Ils ont presque la même signature que dans un ordre différent.

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

essayer

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 définir toutes les erreurs sur SomeException alors je ne dois pas écrire le :: IO (Either SomeException())

attraper / essayer

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

Quand est-ce que j’utilise quelle fonction?

Voici la recommandation de la documentation Control.Exception:

  • Si vous souhaitez effectuer un nettoyage dans le cas où une exception est onException , 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 de try .
  • … sauf si vous récupérez d’une exception asynchrone, auquel cas utilisez catch ou catchJust .

try :: Exception e => IO a -> IO (Soit ea)

try exécute une action IO à exécuter et renvoie un Either . Si le calcul a réussi, le résultat est donné enveloppé dans un constructeur Right . (Pensez bien par opposition à mal). Si l’action a généré une exception du type spécifié , elle est renvoyée dans un constructeur Left . Si l’exception n’est pas du type approprié, elle continue à se propager dans la stack. Spécifier SomeException comme type interceptera toutes les exceptions, ce qui peut être ou ne pas être une bonne idée.

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

catch est similaire à try . Il essaie d’abord d’exécuter l’action IO spécifiée, mais si une exception est levée, le gestionnaire reçoit une exception pour obtenir une autre réponse.

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

Cependant, il y a une différence importante. Lors de l’utilisation de catch votre gestionnaire ne peut pas être interrompu par une exception asynchrone (c’est-à-dire lancée depuis un autre thread via throwTo ). Toute tentative de générer une exception asynchrone bloquera jusqu’à la fin de l’exécution de votre gestionnaire.

Notez qu’il existe une autre catch dans le prélude, vous pouvez donc import Prelude hiding (catch) .

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

handle est simplement catch aux arguments dans l’ordre inverse. La méthode à utiliser dépend de ce qui rend votre code plus lisible ou de celui qui convient le mieux si vous souhaitez utiliser une application partielle. Ils sont par ailleurs identiques.

tryJust, catchJust et handleJust

Notez que try , catch et handle intercepte toutes les exceptions du type spécifié / déduit. tryJust et friends vous permettent de spécifier une fonction de sélection qui filtre les exceptions que vous souhaitez gérer spécifiquement. Par exemple, toutes les erreurs arithmétiques sont de type ArithException . Si vous voulez seulement 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 Ssortingng 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 un code impur (c’est-à-dire la monade IO ). Si vous avez besoin de gérer des erreurs dans du code pur, vous devriez envisager de renvoyer des valeurs en utilisant Maybe ou Either place (ou un autre type de données algébrique). Ceci est souvent préférable car il est plus explicite et vous savez toujours ce qui peut arriver où. Les monades comme Control.Monad.Error facilitent l’utilisation de ce type de gestion des erreurs.


Voir également:

  • Control.Exception

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

Re: question 3: attraper et manipuler sont les mêmes (grâce à 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 pourriez peut-être créer une fonction de gestion pour créer un gestionnaire personnalisé, que vous pouvez ensuite transmettre, 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 de ceci:

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

Pour ça:

 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 que cela en vaut esthétiquement.