Manejo de excepciones en Haskell
exception-handling (4)
Necesito ayuda para entender el uso de las tres funciones de 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
) - manejar (
Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a
)
Necesito saber varias cosas:
- ¿Cuándo uso qué función?
- ¿Cómo uso esta función con un ejemplo simple?
- ¿Dónde está la diferencia entre catch y handle? Tienen casi la misma firma solo con un orden diferente.
Trataré de escribir mis pruebas y espero que puedan ayudarme:
tratar
Tengo un ejemplo como:
x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())
Tengo dos preguntas:
¿Cómo puedo configurar una salida de error personalizada?
¿Qué puedo hacer para establecer todos los errores en SomeException, así que no debo escribir
:: IO (Either SomeException())
atrapar / probar
¿Me puede mostrar un pequeño ejemplo con un resultado de error personalizado?
¿Cuándo uso qué función?
Aquí está la recomendación de la documentación Control.Exception:
- Si desea realizar una limpieza en caso de que se
onException
una excepción, utilicefinally
,bracket
oonException
. - Para recuperarse después de una excepción y hacer otra cosa, la mejor opción es usar uno de la familia
try
. - ... a menos que se esté recuperando de una excepción asincrónica, en cuyo caso use
catch
ocatchJust
.
try :: Exception e => IO a -> IO (E ea ea)
try
toma una acción IO
para ejecutarse, y devuelve un Either
. Si el cálculo tuvo éxito, el resultado se da en un constructor Right
. (Piensa en lo correcto en lugar de lo incorrecto). Si la acción arrojó una excepción del tipo especificado , se devuelve en un constructor Left
. Si la excepción no era del tipo apropiado, continúa propagándose por la pila. Especificar SomeException
como tipo capturará todas las excepciones, lo que puede o no ser una buena idea.
Tenga en cuenta que si desea capturar una excepción de un cálculo puro, tendrá que usar la evaluate
para forzar la evaluación dentro del 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
es similar a try
. Primero intenta ejecutar la acción de IO
especificada, pero si se lanza una excepción, el manejador recibe la excepción para obtener una respuesta alternativa.
main = catch (print $ 5 `div` 0) handler
where
handler :: SomeException -> IO ()
handler ex = putStrLn $ "Caught exception: " ++ show ex
Sin embargo, hay una diferencia importante. Al utilizar catch
su controlador no puede ser interrumpido por una excepción asincrónica (es decir, lanzada desde otro hilo mediante throwTo
). Los intentos de generar una excepción asincrónica se bloquearán hasta que el controlador haya terminado de ejecutarse.
Tenga en cuenta que hay una catch
diferente en el Preludio, por lo que es posible que desee import Prelude hiding (catch)
.
handle :: Exception e => (e -> IO a) -> IO a -> IO a
handle
es simplemente catch
con los argumentos en el orden inverso. Cuál usar depende de qué hace que su código sea más legible, o cuál se ajusta mejor si desea usar una aplicación parcial. Son por lo demás idénticos.
tryJust, catchJust y handleJust
Tenga en cuenta que try
, catch
y handle
detectarán todas las excepciones del tipo especificado / inferido. tryJust
y sus amigos le permiten especificar una función de selector que filtra qué excepciones desea manejar específicamente. Por ejemplo, todos los errores aritméticos son de tipo ArithException
. Si solo quieres capturar DivideByZero
, puedes hacer:
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
Una nota sobre la pureza
Tenga en cuenta que este tipo de manejo de excepciones solo puede ocurrir en código impuro (es decir, la mónada IO
). Si necesita manejar errores en código puro, debe buscar valores de retorno utilizando Maybe
o Either
lugar (o algún otro tipo de datos algebraicos). Esto a menudo es preferible, ya que es más explícito, por lo que siempre sabrá qué puede pasar. Mónadas como Control.Monad.Error
hacen más fácil trabajar con este tipo de manejo de errores.
Ver también:
Edward Z. Yang tiene un artículo sobre manejo de excepciones en Haskell: 8 formas de reportar errores en Haskell revisitados .
Re: pregunta 3: catch y handle son lo same (se encuentra a través de hoogle ). La elección de cuál usar generalmente dependerá de la duración de cada argumento. Si la acción es más corta, use catch y viceversa. Ejemplo de manejo simple de la documentación:
do handle (/NonTermination -> exitWith (ExitFailure 1)) $ ...
Además, podría curry la función de manejar para hacer un controlador personalizado, que luego podría pasar, por ejemplo. (adaptado de la documentación):
let handler = handle (/NonTermination -> exitWith (ExitFailure 1))
Mensajes de error personalizados:
do
let result = 5 `div` 0
let handler = (/_ -> print "Error") :: IOException -> IO ()
catch (print result) handler
Veo que una cosa que también te molesta (tu segunda pregunta) es la escritura de :: IO (Either SomeException ())
y me molestó también.
Cambié un código ahora de esto:
let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
Left _ -> putStrLn "Error"
Right () -> putStrLn "OK"
A esto:
let x = 5 `div` 0
result <- try (print x)
case result of
Left (_ :: SomeException) -> putStrLn "Error"
Right () -> putStrLn "OK"
Para hacer esto, debe usar la extensión ScopedTypeVariables
GHC, pero creo que estéticamente vale la pena.