$ ./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 "Освободите место на диске!"