Manejo de excepciones en Haskell

79

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:

  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 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:

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

  2. ¿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?

develhevel
fuente

Respuestas:

132

¿Cuándo uso qué función?

Aquí está la recomendación de la documentación de Control.Exception:

  • Si usted quiere hacer algo de limpieza en caso de que se produce una excepción, utilizar finally, bracketo onException.
  • Para recuperarse después de una excepción y hacer otra cosa, la mejor opción es utilizar uno de la tryfamilia.
  • ... a menos que se esté recuperando de una excepción asincrónica, en cuyo caso use catcho catchJust.

intente :: Excepción e => IO a -> IO (cualquiera de los dos)

tryrealiza una IOacción para ejecutarse y devuelve un Either. Si el cálculo tuvo éxito, el resultado se entrega envuelto en un Rightconstructor. (Piense bien en lugar de mal). Si la acción arrojó una excepción del tipo especificado , se devuelve en un Leftconstructor. Si la excepción no fue del tipo apropiado, continúa propagándose hacia arriba en la pila. Especificar SomeExceptioncomo 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 evaluatepara forzar la evaluación dentro de 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

catches similar a try. Primero intenta ejecutar la IOacció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, catchsu controlador no puede ser interrumpido por una excepción asincrónica (es decir, arrojado desde otro hilo a través de throwTo). 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 catchen el Preludio, por lo que es posible que desee hacerlo import Prelude hiding (catch).

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

handlees simplemente catchcon 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, catchy handlecogerá todas las excepciones del tipo especificado / inferido. tryJusty 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 tipo ArithException. Si solo quiere atrapar DivideByZero, 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 IOmónada). Si necesita manejar errores en código puro, debería considerar devolver valores usando Maybeo en su Eitherlugar (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 like Control.Monad.Errorhace que este tipo de manejo de errores sea más fácil de trabajar.


Ver también:

Hammar
fuente
8
bastante informativo, pero me sorprende que haya omitido la regla general de los documentos de Control.Exception. Es decir, "use try, a menos que se esté recuperando de una excepción asincrónica, en cuyo caso use catch"
John L
2

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 ScopedTypeVariablesextensión GHC pero creo que estéticamente vale la pena.

Emmanuel Touzery
fuente
1

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
Boris
fuente