Обработка исключений в 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, но я думаю, что эстетически это того стоит.