Мне нужна помощь, чтобы понять использование трех функций 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())
поймать / попробовать
Не могли бы вы показать мне короткий пример с настраиваемым выводом ошибок?
haskell
exception-handling
Develhevel
источник
источник
Ответы:
Когда мне использовать какую функцию?
Вот рекомендация из документации Control.Exception:
finally
,bracket
илиonException
.try
семьи.catch
илиcatchJust
.try :: Exception e => IO a -> IO (Либо ea)
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
catch :: Exception e => IO a -> (e -> IO a) -> IO a
catch
похож наtry
. Сначала он пытается выполнить указанноеIO
действие, но если возникает исключение, обработчику предоставляется исключение для получения альтернативного ответа.main = catch (print $ 5 `div` 0) handler where handler :: SomeException -> IO () handler ex = putStrLn $ "Caught exception: " ++ show ex
Тем не мение, есть одно важное отличие. При использовании
catch
ваш обработчик не может быть прерван асинхронным исключением (то есть брошенным из другого потока черезthrowTo
). Попытки вызвать асинхронное исключение будут блокироваться, пока ваш обработчик не завершит работу.Обратите внимание, что есть другой
catch
в Prelude , так что вы, возможно, захотите это сделатьimport Prelude hiding (catch)
.handle :: Exception e => (e -> IO a) -> IO a -> IO a
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
упрощают работу с этим типом обработки ошибок.Смотрите также:
источник
try
, если вы не восстанавливаетесь после асинхронного исключения, в этом случае используйтеcatch
»Эдвард З. Янг написал статью об обработке исключений в haskell: 8 способов сообщить об ошибках в Haskell .
источник
Я вижу, что вас раздражает еще одна вещь (ваш второй вопрос) - это написание,
:: 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, но я думаю, что эстетически оно того стоит.источник
Re: вопрос 3: защелка и ручка одинаковые (найдено через hoogle ). Выбор того, что использовать, обычно зависит от длины каждого аргумента. Если действие короче, используйте захват и наоборот. Пример простой ручки из документации:
do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...
Кроме того, вы могли бы каррировать функцию дескриптора, чтобы создать собственный обработчик, который затем можно было бы передать, например. (адаптировано из документации):
let handler = handle (\NonTermination -> exitWith (ExitFailure 1))
Пользовательские сообщения об ошибках:
do let result = 5 `div` 0 let handler = (\_ -> print "Error") :: IOException -> IO () catch (print result) handler
источник