Обработка исключений в Haskell
мне нужна помощь, чтобы понять использование трех функций Haskell
- попробовать (
Control.Exception.try :: Exception e => IO a -> IO (Either e a)
) - поймать (
Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a
) - ручка (
Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a
)
мне нужно знать несколько вещей:
- когда я использую функцию?
- как я могу использовать эту функцию с какой-нибудь простой пример?
- где разница между задвижкой и ручкой? Они имеют почти такую же подпись, только с другой порядок.
я постараюсь записать мои испытания и надеюсь, что вы можете мне помочь:
попробовать
у меня есть пример, как:
x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())
у меня есть два вопроса:
как я могу установить пользовательский вывод ошибок?
что я могу сделать, чтобы установить все ошибки в SomeException, поэтому я не должен писать
:: IO (Either SomeException())
поймать/попробовать
вы можете покажите мне короткий пример с пользовательским выводом ошибок?
4 ответа:
когда я использую функцию?
вот рекомендация от управления.Исключение документации:
- если вы хотите сделать некоторую очистку в случае возникновения исключения, используйте
finally
,bracket
илиonException
.- чтобы восстановить после исключения и сделать что-то еще, лучший выбор-использовать один из
try
семья.- ... если вы не восстанавливаетесь из асинхронного исключения, в этом случае используйте
catch
илиcatchJust
.попробовать: исключение e => Ио а -> ИО (либо е)
try
принимаетIO
действие для запуска и возвращаетEither
. Если вычисление прошло успешно, результат задается в оболочкеRight
конструктор. (Думайте правильно, а не неправильно). Если действие вызвало исключение указанного типа, он вернулся вLeft
конструктор. Если исключение было не соответствующего типа, он продолжает вверх по стеку. УказаниеSomeException
как тип будет ловить все исключения, которые могут или не могут быть хорошей идеей.обратите внимание, что если вы хотите поймать исключение из чистого вычисления, вам придется использовать
evaluate
для принудительной оценки в пределах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
лови :: исключение e => ИО А -> (Е -> Ио а) -> Ио в
catch
похож наtry
. Он сначала пытается запустить указанныйIO
действие, но если возникает исключение обработчик получает исключение, чтобы получить альтернативный ответ.main = catch (print $ 5 `div` 0) handler where handler :: SomeException -> IO () handler ex = putStrLn $ "Caught exception: " ++ show ex
, есть одно важное отличие. При использовании
catch
ваш обработчик не может быть прерван асинхронным исключением (т. е. брошенным из другого потока черезthrowTo
). Попытки создать асинхронное исключение будут блокироваться до тех пор, пока обработчик не завершит работу.обратите внимание, что есть разные
catch
в прелюдии, так что вы можете сделатьimport Prelude hiding (catch)
.ручка :: исключение e => (е -> Ио а) -> Ио а -> и. о.
handle
простоcatch
с аргументами в обратном порядке. Какой из них использовать, зависит от того, что делает ваш код более читаемым, или какой из них лучше подходит, если вы хотите использовать частичное приложение. В остальном они идентичны.tryJust, catchJust и handleJust
обратите внимание, что
try
,catch
иhandle
будет ловить все исключения указанный / выводимый тип.tryJust
и друзья позволяют указать функцию селектора, которая отфильтровывает, какие исключения вы конкретно хотите обрабатывать. Например, все арифметические ошибки имеют типArithException
. Если вы только хотите пойматьDivideByZero
, вы можете сделать: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
заметка о чистоте
обратите внимание, что этот тип обработки исключений может происходить только в нечистом коде (т. е.
IO
монады). Если вам нужно обрабатывать ошибки в чистом коде, вы должны посмотреть в возвращаемые значения с помощьюMaybe
илиEither
вместо этого (или какой-либо другой алгебраический тип данных). Это часто предпочтительнее, поскольку это более явно, поэтому вы всегда знаете, что может произойти где. Монады любятControl.Monad.Error
упрощает работу с этим типом обработки ошибок.
Читайте также:
у Эдварда З. Яна есть статья об обработке исключений в haskell: 8 способов сообщить об ошибках в Haskell revisited.
Re: Вопрос 3: поймать и обрабатывать являются то же самое (нашли через hoogle). Этот выбор обычно зависит от длины каждого аргумента. Если действие короче, используйте catch и наоборот. Простой пример дескриптора из документации:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...
кроме того, вы могли бы предположительно Карри функцию дескриптора, чтобы сделать пользовательский обработчик, который вы могли бы затем передать, например. (взято из документации):
let handler = handle (\NonTermination -> exitWith (ExitFailure 1))
Custom сообщения об ошибках:
do let result = 5 `div` 0 let handler = (\_ -> print "Error") :: IOException -> IO () catch (print result) handler
Я вижу, что одна вещь, которая также раздражает вас (ваш второй вопрос) - это сочинение
:: IO (Either SomeException ())
и это тоже раздражало меня.Я изменил некоторый код теперь от этого:
let x = 5 `div` 0 result <- try (print x) :: IO (Either SomeException ()) case result of Left _ -> putStrLn "Error" Right () -> putStrLn "OK"
для этого:
let x = 5 `div` 0 result <- try (print x) case result of Left (_ :: SomeException) -> putStrLn "Error" Right () -> putStrLn "OK"
для этого необходимо использовать
ScopedTypeVariables
расширение GHC, но я думаю, что эстетически это того стоит.