ЗНАЕТЕ ЛИ ВЫ?

Обработка исключений, возникших в чистом коде



В стандарте языка 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 ()) case res of

Left e -> putStrLn "Деление на 0!" Right () -> putStrLn "OK"

 
 

putStrLn "Конец программы"

Погоняем программу на различных значениях:

$ ./quotients 20 7

OK

Конец программы

$ ./quotients 0 7

Деление на 0! Конец программы

$ ./quotients 7 0

 
 

Деление на 0! Конец программы

Понятно, что пока эта программа неустойчива к другим видам ошибок. В частности, мы можем «забыть» передать параметры ко- мандной строки или передать их не в том количестве:

$ ./quotients

quotients: quotients.hs:10:1-31: Non-exhaustive patterns in function params

$ ./quotients 2 3 4

 
 

quotients: quotients.hs:10:1-31: Non-exhaustive patterns in function params

Это исключение генерируется при вызове функции params, если переданный ей список оказывается не двухэлементным. Можно так- же указать нечисловые параметры:

$ ./quotients a b

 
 

quotients: Prelude.read: no parse


 

 
 

Исключение здесь генерируется функцией read, которая не в со- стоянии преобразовать переданный ей параметр к числовому типу. Чтобы справиться с любыми возможными исключениями, выде- лим тело программы в отдельную функцию, оставив в функции main получение параметров командной строки и обработку исключений:

mainAction :: [String] -> IO () mainAction args = do

let (a, b) = params args printQuotients a b

 

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]

 
 

putStrLn "Конец программы"

Посмотрим, как она теперь работает:

$ ./quotients 20 10

Конец программы

$ ./quotients

Неверное число параметров командной строки! Конец программы

$ ./quotients 2 0

Деление на 0! Конец программы

$ ./quotients a b

 
 

Неизвестное исключение: Prelude.read: no parse Конец программы


 

В этом разделе мы разобрались с работой функций try, catch и catches, позволяющих обработать исключение, в том числе и воз- никшее в чистом коде. Заметьте ещё раз, что вся обработка выпол- нялась в рамках действий ввода-вывода. Посмотрим теперь, как работать с исключениями, которые возникают при выполнении операций ввода-вывода.

 





Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 75.101.243.64 (0.008 с.)