Обработка исключений, возникших в чистом коде
В стандарте языка Haskell 98 года присутствует механизм обработки исключений ввода-вывода, который в настоящее время считается устаревшим. Согласно современному подходу все исключения, возникшие как при выполнении чистого кода, так и при осуществлении ввода-вывода, должны обрабатываться единообразно. Этой цели служит единая иерархия типов исключений из модуля >Control.Exception
, в которую легко можно включать собственные типы исключений. Любой тип исключения должен реализовывать экземпляр класса типов >Exception
. В модуле >Control.Exception
объявлено несколько конкретных типов исключений, среди которых >IOException
(исключения ввода-вывода), >ArithException
(арифметические ошибки, например, деление на ноль), >ErrorCall
(вызов функции >error
), >PatternMatchFail
(не удалось выбрать подходящий образец в определении функции) и другие.
Простейший способ выполнить действие, которое потенциально может вызвать исключение,– воспользоваться функцией >try
:
>try :: Exception e => IO a -> IO (Either e a)
Функция >try
пытается выполнить переданное ей действие ввода-вывода и возвращает либо >Right <результат действия>
либо >Left <исключение>
, например:
>ghci> try (print $ 5 `div` 2) :: IO (Either ArithException ())
>2
>Right ()
>ghci> try (print $ 5 `div` 0) :: IO (Either ArithException ())
>Left divide by zero
Обратите внимание, что в данном случае потребовалось явно указать тип выражения, поскольку для вывода типа информации недостаточно. Помимо прочего, указание типа исключения позволяет обрабатывать не все исключения, а только некоторые. В следующем примере исключение функцией >try
обнаружено не будет:
>> try (print $ 5 `div` 0) :: IO (Either IOException ())
>*** Exception: divide by zero
Указание типа >SomeException
позволяет обнаружить любое исключение:
>ghci> try (print $ 5 `div` 0) :: IO (Either SomeException ())
>Left divide by zero
Попробуем написать программу, которая принимает два числа в виде параметров командной строки, делит первое число на второе и наоборот и выводит результаты. Нашей первой целью будет корректная обработка ошибки деления на ноль.
>import Control.Exception
>import System.Environment
>printQuotients :: Integer -> Integer -> IO ()
>printQuotients a b = do
> print $ a `div` b
> print $ b `div` a
>params :: [String] -> (Integer, Integer)
>params [a,b] = (read a, read b)
>main = do
> args <- getArgs
> let (a, b) = params args
> res <- try (printQuotients a b) :: IO (Either ArithException ())