haskell-notes

как обрабатываются ошибки, которые происходят при взаимодействии с внешним миром, ошибки, которые

происходят внутри типа 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

Как только, мы закончим работу с файлом, его необходимо закрыть. Нам нужно освободить дескриптор.

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162