main = do
args <- getArgs
res <- try (mainAction args) :: IO (Either SomeException ())
case res of
Left e -> putStrLn "Ошибка"
Right () -> putStrLn "OK"
putStrLn "Конец программы"
Мы были вынуждены заменить тип исключения на SomeException
и сделать сообщение об ошибке менее информативным, поскольку теперь неизвестно, исключение какого вида в данном случае произошло.
$ ./quotients a b
Ошибка
Конец программы
$ ./quotients
Ошибка
Конец программы
Понятно, что в общем случае обработка исключения должна зависеть от её типа. Предположим, что у нас имеется несколько обработчиков для исключений разных типов:
handleArith :: ArithException -> IO ()
handleArith _ = putStrLn "Деление на 0!"
handleArgs :: PatternMatchFail -> IO ()
handleArgs _ = putStrLn "Неверное число параметров командной строки!"
handleOthers :: SomeException -> IO ()
handleOthers e = putStrLn $ "Неизвестное исключение: " ++ show e
К сожалению, чтобы увидеть исключение от функции read
, нужно воспользоваться наиболее общим типом SomeException
.
Вместо того чтобы вручную вызывать функцию обработчика при анализе результата try
, можно применить функцию catch
, вот её тип:
ghci> :t catch
catch :: Exception e => IO a -> (e -> IO a) -> IO a
ПРИМЕЧАНИЕ. Модуль Prelude
экспортирует старую версию функции catch
, которая способна обрабатывать только исключения ввода-вывода. Чтобы использовать новый вариант её определения, необходимо использовать скрывающий импорт: import Prelude hiding (catch)
.
Функция catch
принимает в качестве параметров действие и обработчик исключения: если при выполнении действия генерируется исключение, то вызывается его обработчик. Тип обработчика определяет, какие именно исключения будут обработаны. Рассмотрим примеры, в которых функция mainAction
вызывается непосредственно в GHCi:
ghci> mainAction ["2","0"]
*** Exception: divide by zero
ghci> mainAction ["0","2"] `catch` handleArith
0
Деление на 0!
ghci> mainAction ["2","0"] `catch` handleArgs
*** Exception: divide by zero
ghci> mainAction ["2","0"] `catch` handleOthers
Неизвестное исключение: divide by zero
ghci> mainAction ["a", "b"] `catch` handleArgs
*** Exception: Prelude.read: no parse
ghci> mainAction ["a", "b"] `catch` handleOthers
Неизвестное исключение: Prelude.read: no parse
Если строка, выводимая GHCi, начинается с ***
, то соответствующее исключение не было обработано. Обратите внимание на обычный для функции catch
инфиксный способ вызова. Заметьте также, что обработчик handleOthers
способен обработать любое исключение.
Вернёмся к основной программе. Нам хочется, чтобы возникшее исключение было обработано наиболее подходящим образом: если произошло деление на ноль, то следует выполнить handleArith
, при неверном числе параметров командной строки – handleArgs
, в остальных случаях – handleOthers
. В этом нам поможет функция catches
, посмотрим на её тип:
> :t catches
catches :: IO a -> [Handler a] -> IO a
Функция catches
принимает в качестве параметров действие и список обработчиков (функций, которые упакованы конструктором данных Handler
) и возвращает результат действия. Если в процессе выполнения происходит исключение, то вызывается первый из подходящих по типу исключения обработчиков (поэтому, в частности, обработчик handleOthers
должен быть последним). Перепишем функцию main
так, чтобы корректно обрабатывались все возможные исключительные ситуации:
main = do
args <- getArgs
mainAction args `catches`
[Handler handleArith,
Handler handleArgs,
Handler handleOthers]