Necesito ayuda para comprender el uso de las tres funciones de Haskell
- probar (
Control.Exception.try :: Exception e => IO a -> IO (Either e a)
) - atrapar (
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 atrapar y manipular? Tienen casi la misma firma solo con un orden diferente.
Intentaré anotar 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 para no tener que escribir el
:: IO (Either SomeException())
atrapar / intentar
¿Puede mostrarme un breve ejemplo con una salida de error personalizada?
haskell
exception-handling
develhevel
fuente
fuente
Respuestas:
¿Cuándo uso qué función?
Aquí está la recomendación de la documentación de Control.Exception:
finally
,bracket
oonException
.try
familia.catch
ocatchJust
.intente :: Excepción e => IO a -> IO (cualquiera de los dos)
try
realiza unaIO
acción para ejecutarse y devuelve unEither
. Si el cálculo tuvo éxito, el resultado se entrega envuelto en unRight
constructor. (Piense bien en lugar de mal). Si la acción arrojó una excepción del tipo especificado , se devuelve en unLeft
constructor. Si la excepción no fue del tipo apropiado, continúa propagándose hacia arriba en la pila. EspecificarSomeException
como tipo detectará todas las excepciones, lo que puede ser una buena idea o no.Tenga en cuenta que si desea capturar una excepción de un cálculo puro, tendrá que usar
evaluate
para forzar la evaluación dentro detry
.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 atry
. Primero intenta ejecutar laIO
acción especificada , pero si se lanza una excepción, el controlador 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, existe una diferencia importante. Cuando se usa,
catch
su controlador no puede ser interrumpido por una excepción asincrónica (es decir, arrojado desde otro hilo a través dethrowTo
). Los intentos de generar una excepción asincrónica se bloquearán hasta que su controlador haya terminado de ejecutarse.Tenga en cuenta que hay algo diferente
catch
en el Preludio, por lo que es posible que desee hacerloimport Prelude hiding (catch)
.handle :: Exception e => (e -> IO a) -> IO a -> IO a
handle
es simplementecatch
con los argumentos en orden inverso. Cuál usar depende de lo que hace que su código sea más legible, o cuál encaja mejor si desea usar una aplicación parcial. Por lo demás, son idénticos.tryJust, catchJust y handleJust
Tenga en cuenta que
try
,catch
yhandle
cogerá todas las excepciones del tipo especificado / inferido.tryJust
y amigos le permiten especificar una función de selección que filtra qué excepciones desea manejar específicamente. Por ejemplo, todos los errores aritméticos son de tipoArithException
. Si solo quiere atraparDivideByZero
, puede 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
IO
mónada). Si necesita manejar errores en código puro, debería considerar devolver valores usandoMaybe
o en suEither
lugar (o algún otro tipo de datos algebraicos). Esto suele ser preferible, ya que es más explícito para que siempre sepa qué puede suceder y dónde. Monads likeControl.Monad.Error
hace que este tipo de manejo de errores sea más fácil de trabajar.Ver también:
fuente
try
, a menos que se esté recuperando de una excepción asincrónica, en cuyo caso usecatch
"Edward Z. Yang tiene un artículo sobre el manejo de excepciones en Haskell: 8 formas de informar errores en Haskell revisado .
fuente
Veo que una cosa que también te molesta (tu segunda pregunta) es la escritura
:: IO (Either SomeException ())
y también me molesta.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, debes usar la
ScopedTypeVariables
extensión GHC pero creo que estéticamente vale la pena.fuente
Re: pregunta 3: atrapar y manejar son lo mismo (encontrado a través de hoogle ) La elección de cuál utilizar dependerá normalmente de la longitud de cada argumento. Si la acción es más corta, use catch y viceversa. Ejemplo de mango simple de la documentación:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...
Además, posiblemente podría utilizar la función de control para hacer un controlador personalizado, que luego podría pasar, por ejemplo. (adaptado de 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
fuente