Ho bisogno di aiuto per capire l’uso delle tre funzioni di Haskell
Control.Exception.try :: Exception e => IO a -> IO (Either ea)
)) Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a
) Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a
) Ho bisogno di sapere diverse cose:
Proverò a scrivere le mie prove e spero che tu possa aiutarmi:
provare
Ho un esempio come:
x = 5 `div` 0 test = try (print x) :: IO (Either SomeException ())
Ho due domande:
Come posso impostare un output di errore personalizzato?
Cosa posso fare per impostare tutti gli errori su SomeException quindi non devo scrivere il :: IO (Either SomeException())
catch / provare
Puoi mostrarmi un breve esempio con un output di errore personalizzato?
Ecco la raccomandazione dalla documentazione Control.Exception:
finally
, bracket
o onException
. try
. catch
o catchJust
. try
ad eseguire un’azione IO
per l’esecuzione e restituisce un Either
. Se il calcolo è riuscito, il risultato è dato avvolto in un costruttore Right
. (Pensa bene al contrario di sbagliato). Se l’azione ha generato un’eccezione del tipo specificato , viene restituita in un costruttore Left
. Se l’eccezione non era del tipo appropriato, continua a propagarsi nello stack. Specificare SomeException
poiché il tipo catturerà tutte le eccezioni, il che potrebbe essere o meno una buona idea.
Si noti che se si desidera rilevare un’eccezione da un calcolo puro, sarà necessario utilizzare evaluate
per forzare la valutazione all’interno 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
è simile a try
. Prima tenta di eseguire l’azione IO
specificata, ma se viene lanciata un’eccezione al gestore viene fornita l’eccezione per ottenere una risposta alternativa.
main = catch (print $ 5 `div` 0) handler where handler :: SomeException -> IO () handler ex = putStrLn $ "Caught exception: " ++ show ex
Tuttavia, c’è una differenza importante. Quando usi catch
tuo gestore non può essere interrotto da un’eccezione asincrona (cioè generata da un’altra discussione tramite throwTo
). I tentativi di generare un’eccezione asincrona verranno bloccati fino a quando il gestore non ha terminato la corsa.
Nota che c’è un altro catch
nel Preludio, quindi potresti voler import Prelude hiding (catch)
.
handle
è semplicemente catch
con gli argomenti nell’ordine inverso. Quale da usare dipende da cosa rende il codice più leggibile, o quale si adatta meglio se si desidera utilizzare un’applicazione parziale. Sono altrimenti identici.
Nota che try
, catch
e handle
rileverà tutte le eccezioni del tipo specificato / inferito. tryJust
e gli amici consentono di specificare una funzione di selezione che filtra le eccezioni che si desidera specificamente gestire. Ad esempio, tutti gli errori aritmetici sono di tipo ArithException
. Se vuoi solo prendere DivideByZero
, puoi fare:
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
Si noti che questo tipo di gestione delle eccezioni può avvenire solo nel codice impuro (ovvero la monade IO
). Se è necessario gestire gli errori nel codice puro, è necessario esaminare i valori restituiti utilizzando Maybe
o Either
(o qualche altro tipo di dati algebrico). Questo è spesso preferibile in quanto è più esplicito in modo da sapere sempre cosa può accadere dove. Monade come Control.Monad.Error
facilitano questo tipo di gestione degli errori.
Guarda anche:
Edward Z. Yang ha un articolo sulla gestione delle eccezioni in haskell: 8 modi per riportare gli errori in Haskell rivisitati .
Ri: domanda 3: cattura e gestire sono gli stessi (trovati attraverso hoogle ). La scelta di quale usare dipenderà generalmente dalla lunghezza di ogni argomento. Se l’azione è più breve, usa la cattura e viceversa. Esempio di handle semplice dalla documentazione:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...
Inoltre, potresti concepire curry la funzione di handle per creare un gestore personalizzato, che potresti poi trasmettere, ad es. (adattato dalla documentazione):
let handler = handle (\NonTermination -> exitWith (ExitFailure 1))
Messaggi di errore personalizzati:
do let result = 5 `div` 0 let handler = (\_ -> print "Error") :: IOException -> IO () catch (print result) handler
Vedo che una cosa che ti infastidisce anche (la tua seconda domanda) è la scrittura di :: IO (Either SomeException ())
e mi :: IO (Either SomeException ())
fastidio anche me.
Ho cambiato un po ‘di codice ora da questo:
let x = 5 `div` 0 result <- try (print x) :: IO (Either SomeException ()) case result of Left _ -> putStrLn "Error" Right () -> putStrLn "OK"
A questa:
let x = 5 `div` 0 result <- try (print x) case result of Left (_ :: SomeException) -> putStrLn "Error" Right () -> putStrLn "OK"
Per fare questo, è necessario utilizzare l’estensione GHC di ScopedTypeVariables
ma penso che esteticamente ne valga la pena.