Обробка винятків у Haskell


79

Мені потрібна допомога, щоб зрозуміти використання трьох функцій Haskell

  • спробувати ( Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • catch ( 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())

зловити / спробувати

Чи можете ви показати мені короткий приклад із користувацьким виведенням помилок?


Відповіді:


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 :: Виняток 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в Прелюдії є інше , тому, можливо, ви захочете це зробити import Prelude hiding (catch).

handle :: Виняток 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замість цього (або іншого алгебраїчного типу даних). Це часто переважно, оскільки воно є більш чітким, тому ви завжди знаєте, що де може статися. Monads like 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: catch і handle однакові (знайдено через hoogle ). Вибір, який використовувати, як правило, залежатиме від тривалості кожного аргументу. Якщо дія коротша, використовуйте catch і навпаки. Приклад простої ручки з документації:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.