Ausnahmebehandlung in Haskell

79

Ich brauche Hilfe, um die Verwendung der drei Haskell-Funktionen zu verstehen

  • try ( Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • 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)

Ich muss verschiedene Dinge wissen:

  1. Wann benutze ich welche Funktion?
  2. Wie verwende ich diese Funktion anhand eines einfachen Beispiels?
  3. Wo ist der Unterschied zwischen Fang und Griff? Sie haben fast die gleiche Unterschrift nur mit einer anderen Reihenfolge.

Ich werde versuchen, meine Prüfungen aufzuschreiben und hoffe, dass Sie mir helfen können:

Versuchen

Ich habe ein Beispiel wie:

x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())

Ich habe zwei Fragen:

  1. Wie kann ich eine benutzerdefinierte Fehlerausgabe festlegen?

  2. Was kann ich tun, um alle Fehler auf SomeException zu setzen, damit ich das nicht schreiben muss? :: IO (Either SomeException())

fangen / versuchen

Können Sie mir ein kurzes Beispiel mit einer benutzerdefinierten Fehlerausgabe zeigen?

develhevel
quelle

Antworten:

132

Wann benutze ich welche Funktion?

Hier ist die Empfehlung aus der Control.Exception-Dokumentation:

  • Wenn Sie einige Bereinigung in dem Fall tun mögen , dass eine Ausnahme ausgelöst wird, verwenden finally, bracketoder onException.
  • Um sich nach einer Ausnahme zu erholen und etwas anderes zu tun, ist es am besten, einen tryFamilienmitglied zu verwenden.
  • ... es sei denn, Sie erholen sich von einer asynchronen Ausnahme. In diesem Fall verwenden Sie catchoder catchJust.

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

tryführt eine IOauszuführende Aktion aus und gibt eine zurück Either. Wenn die Berechnung erfolgreich war, wird das Ergebnis in einen RightKonstruktor eingeschlossen. (Denken Sie richtig statt falsch). Wenn die Aktion eine Ausnahme des angegebenen Typs auslöste , wird sie in einem LeftKonstruktor zurückgegeben. Wenn die Ausnahme nicht vom entsprechenden Typ war, wird sie den Stapel weiter ausbreiten. Wenn Sie SomeExceptionals Typ angeben, werden alle Ausnahmen abgefangen , die möglicherweise eine gute Idee sind oder nicht.

Beachten Sie, dass Sie, wenn Sie eine Ausnahme von einer reinen Berechnung abfangen möchten, die evaluateAuswertung innerhalb der erzwingen müssen 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

catchist ähnlich wie try. Zuerst wird versucht, die angegebene IOAktion auszuführen. Wenn jedoch eine Ausnahme ausgelöst wird, erhält der Handler die Ausnahme, um eine alternative Antwort zu erhalten.

main = catch (print $ 5 `div` 0) handler
  where
    handler :: SomeException -> IO ()
    handler ex = putStrLn $ "Caught exception: " ++ show ex

Es gibt jedoch einen wichtigen Unterschied. Bei Verwendung catchIhres Handlers kann nicht durch eine asynchrone Ausnahme unterbrochen werden (dh von einem anderen Thread über ausgelöst throwTo). Versuche, eine asynchrone Ausnahme auszulösen, werden blockiert, bis Ihr Handler die Ausführung beendet hat.

Beachten Sie, dass es catchim Prelude einen anderen gibt , also möchten Sie vielleicht etwas tun import Prelude hiding (catch).

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

handleist einfach catchmit den Argumenten in umgekehrter Reihenfolge. Welche verwendet werden soll, hängt davon ab, was Ihren Code lesbarer macht oder welche besser passt, wenn Sie eine Teilanwendung verwenden möchten. Sie sind ansonsten identisch.

tryJust, catchJust und handleJust

Beachten Sie, dass try, catchund handlewird fangen alle Ausnahmen von der angegebenen / gefolgert Typ. tryJustMit und Freunden können Sie eine Auswahlfunktion angeben, die herausfiltert, welche Ausnahmen Sie speziell behandeln möchten. Beispielsweise sind alle arithmetischen Fehler vom Typ ArithException. Wenn Sie nur fangen wollen DivideByZero, können Sie:

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

Ein Hinweis zur Reinheit

Beachten Sie, dass diese Art der Ausnahmebehandlung nur in unreinem Code (dh der IOMonade) auftreten kann. Wenn Sie Fehler in reinem Code behandeln müssen, sollten Sie prüfen, ob Werte mit Maybeoder Eitherstattdessen (oder einem anderen algebraischen Datentyp) zurückgegeben werden. Dies ist oft vorzuziehen, da es expliziter ist, sodass Sie immer wissen, was wo passieren kann. Monaden wie Control.Monad.Errorerleichtern die Arbeit mit dieser Art der Fehlerbehandlung.


Siehe auch:

Hammar
quelle
8
ziemlich informativ, aber ich bin überrascht, dass Sie die Faustregel in den Control.Exception-Dokumenten weggelassen haben. Dh "verwenden try, es sei denn, Sie erholen sich von einer asynchronen Ausnahme, in diesem Fall verwenden catch"
John L
2

Ich sehe, dass eine Sache, die dich auch nervt (deine zweite Frage), das Schreiben von :: IO (Either SomeException ())und es hat mich auch geärgert.

Ich habe jetzt einen Code geändert:

let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
    Left _ -> putStrLn "Error"
    Right () -> putStrLn "OK"

Dazu:

let x = 5 `div` 0
result <- try (print x)
case result of
    Left (_ :: SomeException) -> putStrLn "Error"
    Right () -> putStrLn "OK"

Dazu müssen Sie die ScopedTypeVariablesGHC-Erweiterung verwenden, aber ich denke, es lohnt sich ästhetisch.

Emmanuel Touzery
quelle
1

Betreff : Frage 3: Fang und Handle sind gleich (über hoogle gefunden ). Die Wahl der Verwendung hängt normalerweise von der Länge der einzelnen Argumente ab. Wenn die Aktion kürzer ist, verwenden Sie catch und umgekehrt. Einfaches Handle-Beispiel aus der Dokumentation:

do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...

Es ist auch denkbar, dass Sie die Grifffunktion Curry machen, um einen benutzerdefinierten Handler zu erstellen, den Sie dann weitergeben können, z. (aus der Dokumentation übernommen):

let handler = handle (\NonTermination -> exitWith (ExitFailure 1))

Benutzerdefinierte Fehlermeldungen:

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler
Boris
quelle