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
Как только, мы закончим работу с файлом, его необходимо закрыть. Нам нужно освободить дескриптор.
Сделать это можно функцией hClose:
hClose :: Handle -> IO ()
Стандартные функции ввода/вывода, которые мы рассмотрели ранее определены через функции работы
с дескрипторами. Например так мы выводим строку на экран:
putStr
:: String -> IO ()
putStr s
=
hPutStr stdout s
А так читаем строку с клавиатуры:
getLine
:: IO String
getLine
=
hGetLine stdin
В этих функциях используются дескрипторы стандартных потоков данных stdin и stdout. Отметим функ-
цию withFile:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
Она открывает файл в заданном режиме выполняет функцию на его дескрипторе и и закрывает файл.
Например через эту функцию определены функции readFile и appendFile:
appendFile
:: FilePath -> String -> IO ()
appendFile f txt = withFile f AppendMode (\hdl -> hPutStr hdl txt)
writeFile :: FilePath -> String -> IO ()
writeFile f txt = withFile f WriteMode (\hdl -> hPutStr hdl txt)
8.5 Форточка в мир побочных эффектов
В самом начале главы я сказал о том, что из мира IO
нет выхода. Нет функции с типом IO a -> a. На самом деле выход есть. Функция с таким типом живёт в
модуле System.IO.Unsafe:
unsafePerformIO :: IO a -> a
Длинное имя функции намекает на то, что её необходимо использовать с
скольку последствия могут быть непредсказуемыми.