Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Обработка исключений ввода-выводаСодержание книги
Поиск на нашем сайте
Исключения ввода-вывода происходят, когда что-то пошло не так при взаимодействии с внешним миром в действии ввода-вывода, являющемся частью функции 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, чтобы получить строковое представление числа. Это работа- ет – но что получится, если передать программе имя несуществую- щего файла? $./linecount dont_exist.txt linecount: dont_exist.txt: openFile: does not exist (No such file or directory) Ага, получили ошибку от GHC с сообщением, что файла не сущес- твует! Наша программа «упала». Но лучше бы она печатала красивое сообщение, если файл не найден. Как этого добиться? Можно про-
верять существование файла, прежде чем попытаться его открыть, используя функцию doesFileExist из модуля System.Directory. import System.Environment import System.IO import System.Directory
main = do (fileName:_) <– getArgs fileExists <– doesFileExist fileName if fileExists then do contents <– readFile fileName putStrLn $ "В этом файле " ++ show (length (lines contents)) ++ " строк!" else putStrLn "Файл не существует!" Мы делаем вызов fileExists <– doesFileExist fileName, потому что функция doesFileExist имеет тип doesFileExist:: FilePath –> IO Bool; это означает, что она возвращает действие ввода-вывода, со- держащее булевское значение, которое говорит нам, существует ли файл. Мы не можем напрямую использовать функцию doesFileExist в условном выражении. Другим решением было бы использовать исключения. В этом контексте они совершенно уместны. Ошибка при отсутствии фай- ла происходит в момент выполнения действия ввода-вывода, так что его перехват в секции ввода-вывода лёгок и приятен. К тому же, обработка исключений позволяет сделать этот код менее гро- моздким: import Prelude hiding (catch) import Control.Exception import System.Environment
countLines:: String -> IO () countLines fileName = do contents <- readFile fileName putStrLn $ "В этом файле " ++ show (length (lines contents)) ++ " строк!"
handler:: IOException -> IO ()
handler e = putStrLn "У нас проблемы!"
main = do (fileName:_) <- getArgs countLines fileName `catch` handler Здесь мы определяем обработчик handler для всех исключений ввода-вывода и пользуемся функцией catch для перехвата исключе- ния, возникающего в функции countLines. Попробуем: $./linecount linecount.hs В этом файле 17 строк! $./linecount dont_exist.txt У нас проблемы! Исключение ввода-вывода может быть вызвано целым рядом причин, среди которых, помимо отсутствия файла, может быть также отсутствие права на чтение файла или вообще отказ жёстко- го диска. В обработчике мы не проверяли, какой вид исключения IOException получили. Мы просто возвращаем строку "У нас пробле- мы", что бы ни произошло. Простой перехват всех типов исключений в одном обработчи- ке – плохая практика в языке Haskell, так же как и в большинстве других языков. Что если произошло какое-либо другое исключение, которое мы не хотели бы перехватывать, например прерывание программы? Вот почему мы будем делать то же, что делается в дру- гих языках: проверять, какой вид исключения произошел. Если это тот вид, который мы ожидали перехватить, вызовем обработчик. Если это нечто другое, мы не мешаем исключению распространять- ся далее. Давайте изменим нашу программу так, чтобы она перехва- тывала только исключение, вызываемое отсутствием файла: import Prelude hiding (catch) import Control.Exception import System.Environment import System.IO.Error (isDoesNotExistError)
countLines:: String -> IO () countLines fileName = do contents <- readFile fileName putStrLn $ "В этом файле " ++ show (length (lines contents)) ++
" строк!"
handler:: IOException -> IO () handler e | isDoesNotExistError e = putStrLn "Файл не существует!" | otherwise = ioError e
main = do (fileName:_) <- getArgs countLines fileName `catch` handler Программа осталась той же самой, но поменялся обработчик, ко- торый мы изменили таким образом, что он реагирует только на одну группу исключений ввода-вывода. С этой целью мы воспользовались предикатом isDoesNotExistError из модуля System.IO.Error. Мы при- меняем его к исключению, переданному в обработчик, чтобы опре- делить, было ли исключение вызвано отсутствием файла. В данном случае мы используем охранные выражения, но могли бы использо- вать и условное выражение if–then–else. Если исключение вызвано другими причинами, перевызываем исключение с помощью функ- ции ioError. ПРИМЕЧАНИЕ. Функции try, catch, ioError и некоторые другие объявлены одновременно в модулях System.IO.Error (устаревший вариант) и Control.Exception (современный вариант), поэтому подключение обоих модулей (например, для использования пре- дикатов исключений ввода-вывода) требует скрывающего или квалифицированного импорта либо же, как в предыдущем при- мере, явного указания импортируемых функций. Итак, исключение, произошедшее в действии ввода-вывода countLines, но не по причине отсутствия файла, будет перехвачено и перевызвано в обработчике: $./linecount dont_exist.txt Файл не существует! $./linecount norights.txt linecount: noaccess.txt: openFile: permission denied (Permission denied) Существует несколько предикатов, предназначенных для опре- деления вида исключения ввода-вывода: ® isAlreadyExistsError (файл уже существует); ® isDoesNotExistError (файл не существует);
® isAlreadyInUseError (файл уже используется); ® isFullError (не хватает места на диске); ® isEOFError (достигнут конец файла); ® isIllegalOperation (выполнена недопустимая операция); ® isPermissionError (недостаточно прав доступа). Пользуясь этими предикатами, можно написать примерно та- кой обработчик: handler:: IOException -> IO () handler e | isDoesNotExistError e = putStrLn "Файл не существует!" | isPermissionError e = putStrLn "Не хватает прав доступа!" | isFullError e = putStrLn "Освободите место на диске!" | isIllegalOperation e = putStrLn "Караул! Спасите!" | otherwise = ioError e Убедитесь, что вы перевызываете исключение, если оно не под- ходит под ваши критерии; в противном случае ваша программа иног- да будет «падать» молча, что крайне нежелательно. Модуль System.IO.Error также экспортирует функции, которые позволяют нам получать атрибуты исключения, например дескрип- тор файла, вызвавшего исключение, или имя файла. Все эти фун- кции начинаются с префикса ioe; их полный список вы можете найти в документации. Скажем, мы хотим напечатать имя файла в сообщении об ошибке. Значение fileName, полученное при помо- щи функции getArgs, напечатать нельзя, потому что в обработчик передаётся только значение типа IOException и он не знает ни о чём другом. Функция зависит только от своих параметров. Но мы можем вызвать функцию ioeGetFileName, которая по переданному ей ис- ключению возвращает Maybe FilePath. Функция пытается получить из значения исключения имя файла, если такое возможно. Давайте изменим обработчик так, чтобы он печатал полное имя файла, из- за которого возникло исключение (не забудьте включить функцию ioeGetFileName в список импорта для модуля System.IO.Error): handler:: IOException -> IO () handler e | isDoesNotExistError e = case ioeGetFileName e of Just fileName -> putStrLn $ "Файл " ++ fileName ++ " не существует!"
Nothing -> putStrLn "Файл не существует!" | otherwise = ioError e where fileName = ioeGetFileName e В охранном выражении, если предикат isDoesNotExistError вер- нёт значение True, мы использовали выражение case,чтобы вызвать функцию ioeGetFileName с параметром e; затем сделали сопоставле- ние с образцом по возвращённому значению с типом Maybe. Выра- жение case часто используется в случаях, когда вам надо сделать со- поставление с образцом, не создавая новую функцию. Посмотрим, как это сработает: $./linecount dont_exist.txt Файл dont_exists.txt не существует! Вы не обязаны использовать один обработчик для перехвата всех исключений в части кода, работающей с системой ввода-вы- вода. Вы можете перекрыть только отдельные части кода с помо- щью функции catch или перекрывать разные участки кода разными обработчиками, например так: main = do action1 `catch` handler1 action2 `catch` handler2 launchRockets Функция action1 использует функцию handler1 в качестве обработчика, а функция action2 использует handler2. Функция launchRockets не является параметром функции catch, так что любое сгенерированное в ней исключение обрушит нашу програм- му, если только эта функция не использует try или catch внутри себя для обработки собственных ошибок. Конечно же, action1, action2 и launchRockets – это действия ввода-вывода, которые «склеены» друг с другом блоком do и, вероятно, определены где-то в другом месте. Это похоже на блоки try–catch в других языках: вы можете поместить всю вашу программу в один блок try–catch или защищать отдельные участки программы и перехватывать различные исклю- чения для разных участков.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2017-02-17; просмотров: 257; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.148.117.240 (0.009 с.) |