Potrzebuję pomocy, aby zrozumieć użycie trzech funkcji Haskella
- try (
Control.Exception.try :: Exception e => IO a -> IO (Either e a)
) - złapać (
Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a
) - uchwyt (
Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a
)
Muszę wiedzieć kilka rzeczy:
- Kiedy używać której funkcji?
- Jak używać tej funkcji na prostym przykładzie?
- Jaka jest różnica między zaczepem a uchwytem? Mają prawie ten sam podpis, tylko w innej kolejności.
Spróbuję spisać moje próby i mam nadzieję, że możesz mi pomóc:
próbować
Mam taki przykład:
x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())
Mam dwa pytania:
Jak mogę ustawić niestandardowe wyjście błędu?
Co mogę zrobić, aby ustawić wszystkie błędy na SomeException, więc nie muszę pisać
:: IO (Either SomeException())
złap / spróbuj
Czy możesz mi pokazać krótki przykład z niestandardowym wyjściem błędu?
haskell
exception-handling
develhevel
źródło
źródło
Odpowiedzi:
Kiedy używać której funkcji?
Oto zalecenie z dokumentacji Control.Exception:
finally
,bracket
lubonException
.try
rodziny.catch
lubcatchJust
.try :: Exception e => IO a -> IO (albo ea)
try
podejmujeIO
akcję do uruchomienia i zwraca plikEither
. Jeśli obliczenia się powiodły, wynik jest zawijany wRight
konstruktorze. (Myśl dobrze, a nie źle). Jeśli akcja rzuciła wyjątek określonego typu , jest on zwracany wLeft
konstruktorze. Jeśli wyjątek nie był odpowiedniego typu, propaguje dalej w górę stosu. OkreślenieSomeException
jako typu spowoduje przechwycenie wszystkich wyjątków, co może być dobrym pomysłem lub nie.Zauważ, że jeśli chcesz złapać wyjątek z czystego obliczenia, będziesz musiał użyć
evaluate
do wymuszenia oceny wtry
.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
jest podobny dotry
. Najpierw próbuje uruchomić określonąIO
akcję, ale jeśli zostanie zgłoszony wyjątek, program obsługi otrzymuje wyjątek, aby uzyskać alternatywną odpowiedź.main = catch (print $ 5 `div` 0) handler where handler :: SomeException -> IO () handler ex = putStrLn $ "Caught exception: " ++ show ex
Jest jednak jedna ważna różnica. Podczas korzystania
catch
z programu obsługi nie można go przerwać przez wyjątek asynchroniczny (tj. Wyrzucony z innego wątku przezthrowTo
). Próby wywołania asynchronicznego wyjątku będą blokowane do momentu zakończenia działania programu obsługi.Zwróć uwagę, że
catch
w Preludium jest coś innego , więc możesz chcieć to zrobićimport Prelude hiding (catch)
.handle :: Exception e => (e -> IO a) -> IO a -> IO a
handle
jest po prostucatch
z argumentami w odwrotnej kolejności. To, którego użyć, zależy od tego, co sprawia, że twój kod jest bardziej czytelny, lub który pasuje lepiej, jeśli chcesz używać częściowej aplikacji. Poza tym są identyczne.tryJust, catchJust and handleJust
Należy zauważyć, że
try
,catch
ihandle
złapie wszystkie wyjątki podanego / wywnioskować typu.tryJust
i przyjaciele pozwalają ci określić funkcję selektora, która filtruje wyjątki, które chcesz obsłużyć. Na przykład wszystkie błędy arytmetyczne są typuArithException
. Jeśli chcesz tylko złapaćDivideByZero
, możesz: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
Uwaga o czystości
Zauważ, że ten typ obsługi wyjątków może mieć miejsce tylko w przypadku nieczystego kodu (np.
IO
Monady). Jeśli potrzebujesz obsługiwać błędy w czystym kodzie, powinieneś spojrzeć na zwracanie wartości za pomocąMaybe
lubEither
zamiast tego (lub innego algebraicznego typu danych). Jest to często preferowane, ponieważ jest bardziej wyraźne, więc zawsze wiesz, co może się zdarzyć i gdzie.Control.Monad.Error
Takie monady ułatwiają obsługę tego typu błędów.Zobacz też:
źródło
try
, chyba że odzyskujesz po asynchronicznym wyjątku, w takim przypadku użyjcatch
”Edward Z. Yang opublikował artykuł o obsłudze wyjątków w programie haskell: 8 sposobów zgłaszania błędów w programie Haskell Revisited .
źródło
Widzę, że jedną rzeczą, która również cię denerwuje (twoje drugie pytanie) jest pisanie
:: IO (Either SomeException ())
i irytowało mnie to.Zmieniłem teraz kod z tego:
let x = 5 `div` 0 result <- try (print x) :: IO (Either SomeException ()) case result of Left _ -> putStrLn "Error" Right () -> putStrLn "OK"
Do tego:
let x = 5 `div` 0 result <- try (print x) case result of Left (_ :: SomeException) -> putStrLn "Error" Right () -> putStrLn "OK"
Aby to zrobić, musisz użyć
ScopedTypeVariables
rozszerzenia GHC, ale myślę, że estetycznie warto.źródło
Re: pytanie 3: zaczep i uchwyt są takie same (znalezione przez hoogle ). Wybór, którego użyć, będzie zwykle zależał od długości każdego argumentu. Jeśli akcja jest krótsza, użyj haczyka i odwrotnie. Prosty przykład klamki z dokumentacji:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...
Można również przypuszczalnie użyć funkcji handle, aby utworzyć niestandardową procedurę obsługi, którą można następnie przekazać, np. (na podstawie dokumentacji):
let handler = handle (\NonTermination -> exitWith (ExitFailure 1))
Niestandardowe komunikaty o błędach:
do let result = 5 `div` 0 let handler = (\_ -> print "Error") :: IOException -> IO () catch (print result) handler
źródło