Система ввода - вывода в Haskell является чисто функциональной, но при этом обладает выразительной мощью обычных языков программирования. Чтобы достичь этого, Haskell использует монаду для интеграции операций ввода - вывода в чисто функциональный контекст.
Монада ввода - вывода используется в Haskell как связующее звено между значениями, присущими функциональному языку, и действиями, характеризующими операции ввода - вывода и императивное программирование в общем. Порядок вычисления выражений в Haskell ограничен только зависимостями данных; реализация обладает значительной свободой в выборе этого порядка. Действия, тем не менее, должны быть упорядочены определенным образом для выполнения программы и, в частности, ввода - вывода, для того чтобы быть правильно интерпретированы. В Haskell монада ввода - вывода предоставляет пользователю способ указать последовательное связывание действий, и реализация обязана соблюдать этот порядок.
Термин монада происходит из отрасли математики, известной как теория категорий. Однако, с точки зрения программиста Haskell , лучше думать о монаде как об абстрактном типе данных. В случае монады ввода - вывода абстрактными значениями являются упомянутые выше действия. Некоторые операции являются примитивными действиями, соответствующими обычным операциям ввода - вывода. Специальные операции (методы в классе Monad, см. раздел 6.3.6) последовательно связывают действия, соответствующие последовательным операторам (таким как точка с запятой) в императивных языках.
Все функции ввода - вывода, описанные здесь, имеют дело с символами. Обработка символа новой строки будет различаться в различных системах. Например, два символа ввода, возврат каретки и перевод строки, могут быть считаны как один символ новой строки. Эти функции нельзя использовать в переносимых программах для бинарного ввода - вывода.
Далее, вспомним, что String является синонимом для [Char] (раздел 6.1.2).
Например, программа для печати первых 20 целых чисел и их
степеней 2 могла быть записана так:
main = print ([(n, 2^n) | n <- [0..19]])
Операция getContents возвращает весь пользовательский ввод в виде одной строки, которая считывается лениво, по мере надобности. Функция interact принимает в качестве аргумента функцию типа String->String. Весь ввод из стандартного устройства ввода передается этой функции в качестве аргумента, а результирующая строка выводится на стандартное устройство вывода.
Обычно операция read из класса Read используется для преобразования строки в значение. Функция readIO похожа на read, за исключением того, что она предупреждает монаду ввода - вывода об ошибке разбора вместо завершения программы. Функция readLn объединяет getLine и readIO.
Следующая программа просто удаляет все символы, не являющиеся ASCII, из своего
стандартного ввода и отображает результат на своем стандартном выводе.
(Функция isAscii определена в библиотеке.)
main = interact (filter isAscii)
Функции writeFile и appendFile соответственно записывают или добавляют в конец строку,
свой второй аргумент, в файл, свой первый аргумент.
Функция readFile считывает файл и
возвращает содержимое файла в виде строки. Файл считывается
лениво, по требованию, как в getContents.
type FilePath = String
writeFile :: FilePath -> String -> IO ()
appendFile :: FilePath -> String -> IO ()
readFile :: FilePath -> IO String
Обратите внимание, что writeFile и appendFile записывают литеральную строку
в файл. Для того чтобы записать значение любого пригодного для печати типа, как в print, сначала используется
функция show для преобразования значения в строку.
main = appendFile "квадраты" (show [(x,x*x) | x <- [0,0.1..2]])
Нотация do позволяет программировать в более императивном синтаксическом
стиле. Слегка более сложной версией предыдущего примера
была бы программа:
main = do
putStr "Файл ввода: "
ifile <- getLine
putStr "Файл вывода: "
ofile <- getLine
s <- readFile ifile
writeFile ofile (filter isAscii s)
putStr "Фильтрация завершилась успешно\n"
Функция return используется для определения результата операции ввода - вывода.
Например, getLine определена в терминах getChar,
используя return для определения результата:
getLine :: IO String
getLine = do c <- getChar
if c == '\n' then return ""
else do s <- getLine
return (c:s)
Монада ввода - вывода включает простую систему обработки исключений. Любая операция ввода - вывода может вызвать исключение вместо возвращения результата.
Исключения в монаде ввода - вывода представлены значениями
типа IOError. Это абстрактный тип: его конструкторы скрыты
от пользователя. Библиотека IO определяет функции, которые конструируют и
изучают значения IOError . Единственной функцией Prelude, которая создает
значение IOError, является userError. Пользовательские значения ошибок включают строку с
описанием ошибки.
userError :: String -> IOError
Исключения вызываются и отлавливаются с помощью следующих функций:
ioError :: IOError -> IO a
catch :: IO a -> (IOError -> IO a) -> IO a
Функция ioError вызывает исключение;
функция catch устанавливает обработчик, который получает любое
исключение, вызванное действием, защищенным catch. Исключение
отлавливается самым последним обработчиком, установленным catch. Эти
обработчики не действуют выборочно: они отлавливают все исключения.
Распространение исключения
нужно явно обеспечить в обработчике путем повторного вызова любого
нежелательного исключения. Например, в
f = catch g (\e -> if IO.isEOFError e then return [] else ioError e)
функция f возвращает [], когда в g возникает исключение конца файла,
иначе исключение передается следующему
внешнему обработчику. Функция isEOFError является частью библиотеки IO.
Когда исключение передается за пределы главной программы, система Haskell выводит связанное с ним значение IOError и выходит из программы.
Метод fail экземпляра IO класса Monad (раздел 6.3.6) вызывает
userError так:
instance Monad IO where
...bindings for return, (>>=), (>>)
fail s = ioError (userError s)
Исключения, вызванные функциями ввода - вывода в Prelude, описаны
в главе 21.