Обработка исключений в Haskell

79

Мне нужна помощь, чтобы понять использование трех функций 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)

Мне нужно знать несколько вещей:

  1. Когда мне использовать какую функцию?
  2. Как использовать эту функцию на простом примере?
  3. В чем разница между захватом и ручкой? У них почти одинаковая подпись только в другом порядке.

Я постараюсь записать свои испытания и надеюсь, что вы мне поможете:

пытаться

У меня есть такой пример:

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

У меня два вопроса:

  1. Как я могу установить собственный вывод ошибок?

  2. Что я могу сделать, чтобы установить для всех ошибок значение SomeException, чтобы не писать :: IO (Either SomeException())

поймать / попробовать

Не могли бы вы показать мне короткий пример с настраиваемым выводом ошибок?

Develhevel
источник

Ответы:

132

Когда мне использовать какую функцию?

Вот рекомендация из документации 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упрощают работу с этим типом обработки ошибок.


Смотрите также:

хаммар
источник
8
довольно информативно, но я удивлен, что вы упустили практическое правило из документации Control.Exception. Т.е. «используйте try, если вы не восстанавливаетесь после асинхронного исключения, в этом случае используйте catch»
Джон Л.
2

Я вижу, что вас раздражает еще одна вещь (ваш второй вопрос) - это написание, :: 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, но я думаю, что эстетически оно того стоит.

Эммануэль Тузери
источник
1

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
Борис
источник