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

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

Читать бесплатно Миран Липовача - Изучай Haskell во имя добра!. Жанр: Программирование издательство -, год 2004. Так же читаем полные версии (весь текст) онлайн без регистрации и SMS на сайте kniga-online.club или прочесть краткое содержание, предисловие (аннотацию), описание и ознакомиться с отзывами (комментариями) о произведении.
Перейти на страницу:

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 не существует!

Перейти на страницу:

Миран Липовача читать все книги автора по порядку

Миран Липовача - все книги автора в одном месте читать по порядку полные версии на сайте онлайн библиотеки kniga-online.club.


Изучай Haskell во имя добра! отзывы

Отзывы читателей о книге Изучай Haskell во имя добра!, автор: Миран Липовача. Читайте комментарии и мнения людей о произведении.


Уважаемые читатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.

  • 1. Просьба отказаться от дискриминационных высказываний. Мы защищаем право наших читателей свободно выражать свою точку зрения. Вместе с тем мы не терпим агрессии. На сайте запрещено оставлять комментарий, который содержит унизительные высказывания или призывы к насилию по отношению к отдельным лицам или группам людей на основании их расы, этнического происхождения, вероисповедания, недееспособности, пола, возраста, статуса ветерана, касты или сексуальной ориентации.
  • 2. Просьба отказаться от оскорблений, угроз и запугиваний.
  • 3. Просьба отказаться от нецензурной лексики.
  • 4. Просьба вести себя максимально корректно как по отношению к авторам, так и по отношению к другим читателям и их комментариям.

Надеемся на Ваше понимание и благоразумие. С уважением, администратор kniga-online.


Прокомментировать
Подтвердите что вы не робот:*
Подтвердите что вы не робот:*