Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь 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; просмотров: 326; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 216.73.217.21 (0.009 с.) |