haskell exception-handling

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:

  1. ¿Cuándo uso qué función?
  2. ¿Cómo uso esta función con un ejemplo simple?
  3. ¿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:

  1. ¿Cómo puedo configurar una salida de error personalizada?

  2. ¿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, utilice finally , bracket o onException .
  • 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 o catchJust .

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:



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.