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
, чтобы получить строковое представление числа. Это работает – но что получится, если передать программе имя несуществующего файла?
$ ./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
.
Попробуем: