Gestione delle eccezioni in Haskell

Ho bisogno di aiuto per capire l’uso delle tre funzioni di Haskell

  • provare ( Control.Exception.try :: Exception e => IO a -> IO (Either ea) ))
  • catch ( Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a )
  • handle ( Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a )

Ho bisogno di sapere diverse cose:

  1. Quando utilizzo la funzione?
  2. Come si usa questa funzione con un semplice esempio?
  3. Dov’è la differenza tra la presa e la maniglia? Hanno quasi la stessa firma solo con un ordine diverso.

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:

  1. Come posso impostare un output di errore personalizzato?

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

Quando utilizzo la funzione?

Ecco la raccomandazione dalla documentazione Control.Exception:

  • Se si desidera eseguire una pulizia nel caso in cui venga sollevata un’eccezione, utilizzare finally , bracket o onException .
  • Per recuperare dopo un’eccezione e fare qualcos’altro, la scelta migliore è usare una delle famiglie di try .
  • … a meno che tu non stia recuperando da un’eccezione asincrona, nel qual caso usa catch o catchJust .

try :: Exception e => IO a -> IO (o ea)

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 :: Exception e => IO a -> (e -> IO a) -> IO a

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 :: Exception e => (e -> IO a) -> IO a -> IO a

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.

provare, afferrare e manipolare semplicemente

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 

Una nota sulla purezza

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:

  • Control.Exception

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.