`
Читать книги » Книги » Компьютеры и Интернет » Программирование » Миран Липовача - Изучай Haskell во имя добра!

Миран Липовача - Изучай Haskell во имя добра!

1 ... 49 50 51 52 53 ... 96 ВПЕРЕД
Перейти на страницу:

Простейший способ выполнить действие, которое потенциально может вызвать исключение,– воспользоваться функцией 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

2

0

OK

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

$ ./quotients 0 7

0

Деление на 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

2

0

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

$ ./quotients

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

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

$ ./quotients 2 0

Деление на 0!

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

$ ./quotients a b

Неизвестное исключение: Prelude.read: no parse

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

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

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

Исключения ввода-вывода происходят, когда что-то пошло не так при взаимодействии с внешним миром в действии ввода-вывода, являющемся частью функции main. Например, мы пытаемся открыть файл, и тут оказывается, что он был удалён, или ещё что-нибудь в этом духе. Посмотрите на программу, открывающую файл, имя которого передаётся в командной строке, и говорящую нам, сколько строк содержится в файле:

import System.Environment

import System.IO

main = do

   (fileName:_) <– getArgs

   contents <– readFile fileName

   putStrLn $ "В этом файле " ++ show (length (lines contents)) ++

              " строк!"

Очень простая программа. Мы выполняем действие ввода-вывода getArgs и связываем первую строку в возвращённом списке с идентификатором fileName. Затем связываем имя contents с содержимым файла. Применяем функцию lines к contents, чтобы получить список строк, считаем их количество и передаём его функции show, чтобы получить строковое представление числа. Это работает – но что получится, если передать программе имя несуществующего файла?

1 ... 49 50 51 52 53 ... 96 ВПЕРЕД
Перейти на страницу:

Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Миран Липовача - Изучай Haskell во имя добра!, относящееся к жанру Программирование. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.

Комментарии (0)