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

Мне нужна помощь, чтобы понять использование трех функций 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)
  • handle (Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a)

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

  • Когда я использую эту функцию?
  • Как использовать эту функцию с помощью простого примера?
  • Где разница между catch и handle? У них почти одинаковая подпись только с другим порядком.

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

попробовать

У меня есть пример вроде:

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

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

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

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

улов/попробуйте

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

Ответ 1

Когда я использую какую функцию?

Здесь приведена рекомендация из документации Control.Exception:

  • Если вы хотите выполнить некоторую очистку в случае возникновения исключения, используйте finally, bracket или onException.
  • Чтобы восстановить после исключения и сделать что-то еще, лучший выбор - использовать одно из семейств try.
  • ... если вы не восстанавливаете асинхронное исключение, в этом случае используйте catch или catchJust.

try:: Exception e = > IO a → IO (Либо e a)

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, поэтому вы можете сделать 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 вместо (или какой-либо другой алгебраический тип данных). Это часто предпочтительнее, чем более явное, поэтому вы всегда знаете, что может случиться там. Monads, как Control.Monad.Error, облегчает работу с этим типом обработки ошибок.


См. также:

Ответ 3

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

Ответ 4

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