как обрабатываются ошибки, которые происходят при взаимодействии с внешним миром, ошибки, которые
происходят внутри типа IO.
Ошибки функций с побочными эффектами обрабатываются с помощью специальной функции catch, она
определена в Prelude:
catch :: IO a -> (IOError -> IO a) -> IO a
Эта функция принимает значение, которое содержит побочные эффекты и функцию, которая обрабаты-
вает исключительные ситуации. К примеру если мы попытаемся прочитать данные из файла, к которому у
нас нет доступа, произойдёт ошибка. Мы можем не дать программе упасть и обработать ошибку с помощью
функции catch.
Например программа, в которой мы дописывали данные в файл, упадёт, если мы передадим не существу-
ющий файл. Но мы можем исправить это поведение с помощью функции catch. Мы можем перезапускать
программу, если произошла ошибка:
module FileSafe where
import Control.Applicative
import Control.Monad
main = try ‘catch‘ const main
try = msg1 >> getLine >>= read >>= append
where read
file = readFile file >>= putStrLn >> return file
append file = msg2 >> getLine >>= appendFile file
msg1
= putStr ”input file: ”
msg2
= putStr ”input text: ”
Часто функции двух аргументов называют так, чтобы при инфиксной форме записи получалась фраза
из английского языка. Так если мы запишем catch в инфиксной форме получится очень наглядное выраже-
ние. Функция обработки ошибок реагирует на любую ошибку перезапуском программы. Попробуем взломать
программу:
*FileSafe> main
input file: fsldfksld
input file: sd;fls;dfl;vll; d;fld;f
input file: dflks;ldkf ldkfldkfld
input file: lsdkfksdlf ksdkflsdfkls;dfk
input file: bfk
input file: test
Hello!Hi)
input text: HowHow
Функция будет запрашивать файл до тех пор, пока мы не введём корректное значение. Мы можем доба-
вить сообщение об ошибке, немного изменив функцию обработки:
main = try ‘catch‘ const (msg >> main)
where msg = putStrLn ”Wrong filename, try again.”
А что делать если нам хочется различать ошибки по типу и предпринимать различные действия в зави-
симости от типа ошибки? Ошибки распознаются с помощью специальных предикатов, которые определены
в модуле System.IO.Error. Рассмотрим некоторые из них.
136 | Глава 8: IO
Например с помощью с помощью предиката isDoesNotExistErrorType мы можем опознать ошибки,
которые случились из-за того, что один из аргументов функции не существует. С помощью предиката
isPermissionErrorType мы можем узнать, что ошибка произошла из-за того, что мы пытались получить до-
ступ к данным, на которые у нас нет прав. Мы можем, немного изменив функцию-обработчик исключений,
выводить более информативные сообщения об ошибках перед перезапуском:
main = try ‘catch‘ handler
handler :: IOError -> IO ()
handler = ( >> main) . putStrLn . msg2 . msg1
msg1 e
| isDoesNotExistErrorType e = ”File does not exist. ”
| isPermissionErrorType e
= ”Access denied. ”
| otherwise
= ””
msg2 = (++ ”Try again.”)
В модуле System.IO.Error вы можете найти ещё много разных предикатов.
Потоки текстовых данных
Обмен данными, чтение и запись происходят с помощью потоков. Каждый поток имеет дескриптор
(handle), через него мы можем общаться с потоком, например считывать данные или записывать. Функции
для работы с потоками данных определены в модуле System.IO.
В любой момент в системе открыты три стандартных потока:
• stdin – стандартный ввод
• stdout – стандартный вывод
• stderr – поток ошибок и отладочных сообщений
Например когда мы выводим строку на экран, на самом деле мы записываем строку в поток stdout. А
когда мы читаем символ с клавиатуры, мы считываем его из потока stdin.
Файлы также являются потоками. При открытии файлу присваивается дескриптор через который, мы
можем обмениваться данными. Файл может быть открыт для чтения, записи, дополнения (записи в конец
файла) или чтения и записи. Файл открывается функцией:
openFile :: FilePath -> IOMode -> IO Handle
Функция принимает путь к файлу и режим работы с файлом и возвращает дескриптор. Режим может
принимать одно из значений:
• ReadMode – чтение
• WriteMode – запись
• AppendMode – добавление (запись в конец файла)
• ReadWriteMode – чтение и запись
Открыв дескриптор, мы можем начать обмениваться данными. Для этого определены функции аналогич-
ные тем, что мы уже рассмотрели. Функции для записи данных:
— запись символа
hPutChar :: Handle -> Char -> IO ()
— запись строки
hPutStr :: Handle -> String -> IO ()
— запись строки с переносом каретки
hPutStrLn :: Handle -> String -> IO ()
— запись значения
hPrint :: Show a => Handle -> a -> IO ()
Типичные задачи IO | 137
Все функции принимают первым аргументом дескриптор потока. Дескриптор должен позволять записы-
вать данные. Например для дескриптора, открытого в режиме ReadMode, выполнение этих функций приведёт
к ошибке.
Из потоков также можно читать данные. Эти функции похожи на те, что мы уже рассмотрели:
— чтение одного символа
hGetChar :: Handle -> IO Char
— чтение строки
hGetLine :: Handle -> IO String
— ленивое чтение строки
hGetContents :: Handle -> IO String
Как только, мы закончим работу с файлом, его необходимо закрыть. Нам нужно освободить дескриптор.