Выбрать главу

нас нет доступа, произойдёт ошибка. Мы можем не дать программе упасть и обработать ошибку с помощью

функции 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

Все функции принимают первым аргументом дескриптор потока. Дескриптор должен позволять записы-