Описание Haskell 98: Основные операции ввода - вывода Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций


7  Основные операции ввода - вывода

Система ввода - вывода в Haskell является чисто функциональной, но при этом обладает выразительной мощью обычных языков программирования. Чтобы достичь этого, Haskell использует монаду для интеграции операций ввода - вывода в чисто функциональный контекст.

Монада ввода - вывода используется в Haskell как связующее звено между значениями, присущими функциональному языку, и действиями, характеризующими операции ввода - вывода и императивное программирование в общем. Порядок вычисления выражений в Haskell ограничен только зависимостями данных; реализация обладает значительной свободой в выборе этого порядка. Действия, тем не менее, должны быть упорядочены определенным образом для выполнения программы и, в частности, ввода - вывода, для того чтобы быть правильно интерпретированы. В Haskell монада ввода - вывода предоставляет пользователю способ указать последовательное связывание действий, и реализация обязана соблюдать этот порядок.

Термин монада происходит из отрасли математики, известной как теория категорий. Однако, с точки зрения программиста Haskell , лучше думать о монаде как об абстрактном типе данных. В случае монады ввода - вывода абстрактными значениями являются упомянутые выше действия. Некоторые операции являются примитивными действиями, соответствующими обычным операциям ввода - вывода. Специальные операции (методы в классе Monad, см. раздел 6.3.6) последовательно связывают действия, соответствующие последовательным операторам (таким как точка с запятой) в императивных языках.

7.1  Стандартные функции ввода - вывода

Хотя Haskell обеспечивает довольно сложные средства ввода - вывода, определенные в библиотеке IO, многие программы на Haskell можно писать, используя лишь несколько простых функций, которые экспортируются из Prelude и которые описаны в этом разделе.

Все функции ввода - вывода, описанные здесь, имеют дело с символами. Обработка символа новой строки будет различаться в различных системах. Например, два символа ввода, возврат каретки и перевод строки, могут быть считаны как один символ новой строки. Эти функции нельзя использовать в переносимых программах для бинарного ввода - вывода.

Далее, вспомним, что String является синонимом для [Char] (раздел 6.1.2).

Функции вывода

Эти функции записывают в стандартное устройство вывода (обычно это пользовательский терминал).

  putChar  :: Char -> IO ()
  putStr   :: String -> IO ()
  putStrLn :: String -> IO ()  -- добавляет символ новой строки
  print    :: Show a => a -> IO ()

Функция print выводит значение любого пригодного для печати типа на стандартное устройство вывода. Пригодные для печати типы --- это те типы, которые являются экземплярами класса Show; print преобразует значения в строки для вывода, используя операцию show, и добавляет символ новой строки.

Например, программа для печати первых 20 целых чисел и их степеней 2 могла быть записана так:

main = print ([(n, 2^n) | n <- [0..19]])

Функции ввода

Эти функции считывают данные из стандартного устройства ввода (обычно это пользовательский терминал).

  getChar     :: IO Char
  getLine     :: IO String
  getContents :: IO String
  interact    :: (String -> String) -> IO ()
  readIO      :: Read a => String -> IO a
  readLn      :: Read a => IO a

Операция getChar вызывает исключение (раздел 7.3) при появлении признака конца файла, a предикат isEOFError, который распознает это исключение, определен в библиотеке IO. Операция getLine вызывает исключение при тех же обстоятельствах, что и hGetLine, определенная в библиотеке IO.

Операция 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]])

7.2  Последовательные операции ввода - вывода

Конструктор типа IO является экземпляром класса Monad. Две монадические связывающие функции, методы в классе Monad, используются для составления последовательностей операций ввода - вывода. Функция >> используется там, где результат первой операции не представляет интереса, например, когда он представляет собой (). Операция >>= передает результат первой операции в качестве аргумента второй операции.

  (>>=) :: IO a -> (a -> IO b) -> IO b 
  (>>)  :: IO a -> IO b        -> IO b

Например, программа

main = readFile "input-file"                       >>= \ s ->
       writeFile "output-file" (filter isAscii s)  >>
       putStr "Фильтрация завершилась успешно\n"

похожа на предыдущий пример, использующий interact, но получает свой ввод из "input-file" и записывает свой вывод в "output-file". Перед завершением программы на стандартный вывод распечатывается сообщение.

Нотация 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)

7.3  Обработка исключений в монаде ввода - вывода

Монада ввода - вывода включает простую систему обработки исключений. Любая операция ввода - вывода может вызвать исключение вместо возвращения результата.

Исключения в монаде ввода - вывода представлены значениями типа 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.


Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций
Декабрь 2002