main =
try ‘catch‘ const (msg >> main)where
msg = putStrLn ”Wrong filename, try again.”А что делать если нам хочется различать ошибки по типу и предпринимать различные действия в зави-
симости от типа ошибки? Ошибки распознаются с помощью специальных предикатов, которые определены
в модуле System.IO.Error
. Рассмотрим некоторые из них.136 | Глава 8: IO
Например с помощью с помощью предиката isDoesNotExistErrorType мы можем опознать ошибки,
которые случились из-за того, что один из аргументов функции не существует. С помощью предиката
isPermissionErrorType мы можем узнать, что ошибка произошла из-за того, что мы пытались получить до-
ступ к данным, на которые у нас нет прав. Мы можем, немного изменив функцию-обработчик исключений,
выводить более информативные сообщения об ошибках перед перезапуском:
main =
try ‘catch‘ handlerhandler :: IOError -> IO
()handler =
( >> main) . putStrLn . msg2 . msg1msg1 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Длинное имя функции намекает на то, что её необходимо использовать с
скольку последствия могут быть непредсказуемыми.