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

21  Ввод - вывод


module IO (
    Handle, HandlePosn,
    IOMode(ReadMode,WriteMode,AppendMode,ReadWriteMode),
    BufferMode(NoBuffering,LineBuffering,BlockBuffering),
    SeekMode(AbsoluteSeek,RelativeSeek,SeekFromEnd),
    stdin, stdout, stderr, 
    openFile, hClose, hFileSize, hIsEOF, isEOF,
    hSetBuffering, hGetBuffering, hFlush, 
    hGetPosn, hSetPosn, hSeek, 
    hWaitForInput, hReady, hGetChar, hGetLine, hLookAhead, 
    hGetContents, hPutChar, hPutStr, hPutStrLn, hPrint,
    hIsOpen, hIsClosed, hIsReadable, hIsWritable, hIsSeekable,
    isAlreadyExistsError, isDoesNotExistError, isAlreadyInUseError, 
    isFullError, isEOFError,
    isIllegalOperation, isPermissionError, isUserError, 
    ioeGetErrorString, ioeGetHandle, ioeGetFileName,
    try, bracket, bracket_,

    -- ...и то, что экспортирует Prelude
    IO, FilePath, IOError, ioError, userError, catch, interact,
    putChar, putStr, putStrLn, print, getChar, getLine, getContents,
    readFile, writeFile, appendFile, readIO, readLn
    ) where

import Ix(Ix)

data Handle = ... -- зависит от реализации
instance Eq Handle where ...
instance Show Handle where ..           -- зависит от реализации

data HandlePosn = ... -- зависит от реализации
instance Eq HandlePosn where ...
instance Show HandlePosn where ---      -- зависит от реализации


data IOMode      =  ReadMode | WriteMode | AppendMode | ReadWriteMode
                    deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)
data BufferMode  =  NoBuffering | LineBuffering 
                 |  BlockBuffering (Maybe Int)
                    deriving (Eq, Ord, Read, Show)
data SeekMode    =  AbsoluteSeek | RelativeSeek | SeekFromEnd
                    deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)

stdin, stdout, stderr :: Handle

openFile              :: FilePath -> IOMode -> IO Handle
hClose                :: Handle -> IO ()


hFileSize             :: Handle -> IO Integer
hIsEOF                :: Handle -> IO Bool
isEOF                 :: IO Bool
isEOF                 =  hIsEOF stdin

hSetBuffering         :: Handle  -> BufferMode -> IO ()
hGetBuffering         :: Handle  -> IO BufferMode
hFlush                :: Handle -> IO () 
hGetPosn              :: Handle -> IO HandlePosn
hSetPosn              :: HandlePosn -> IO () 
hSeek                 :: Handle -> SeekMode -> Integer -> IO () 

hWaitForInput       :: Handle -> Int -> IO Bool
hReady                :: Handle -> IO Bool 
hReady h       =  hWaitForInput h 0
hGetChar              :: Handle -> IO Char
hGetLine              :: Handle -> IO String
hLookAhead            :: Handle -> IO Char
hGetContents          :: Handle -> IO String
hPutChar              :: Handle -> Char -> IO ()
hPutStr               :: Handle -> String -> IO ()
hPutStrLn             :: Handle -> String -> IO ()
hPrint                :: Show a => Handle -> a -> IO ()

hIsOpen               :: Handle -> IO Bool
hIsClosed             :: Handle -> IO Bool
hIsReadable           :: Handle -> IO Bool
hIsWritable           :: Handle -> IO Bool
hIsSeekable           :: Handle -> IO Bool

isAlreadyExistsError  :: IOError -> Bool
isDoesNotExistError   :: IOError -> Bool
isAlreadyInUseError   :: IOError -> Bool
isFullError           :: IOError -> Bool
isEOFError            :: IOError -> Bool
isIllegalOperation    :: IOError -> Bool
isPermissionError     :: IOError -> Bool
isUserError           :: IOError -> Bool

ioeGetErrorString     :: IOError -> String
ioeGetHandle          :: IOError -> Maybe Handle
ioeGetFileName        :: IOError -> Maybe FilePath

try                   :: IO a -> IO (Either IOError a)
bracket               :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket_              :: IO a -> (a -> IO b) -> IO c -> IO c

Монадическая система ввода - вывода, используемая в Haskell , описана в описании языка Haskell . Общеупотребительные функции ввода - вывода, такие как print, являются частью стандартного начала (Prelude) и нет необходимости их явно импортировать. Эта библиотека содержит более продвинутые средства ввода - вывода. Некоторые относящиеся к ним операции над файловыми системами содержатся в библиотеке Directory.

21.1  Ошибки ввода - вывода

Ошибки типа IOError используются монадой ввода - вывода. Это абстрактный тип; библиотека обеспечивает функции для опроса и конструирования значений в IOError: Все эти функции возвращают значение типа Bool, которое равно True, если ее аргументом является соответствующий вид ошибки, и False иначе.

Любая функция, которая возвращает результат IO, может завершиться с ошибкой isIllegalOperation. Дополнительные ошибки, которые могут быть вызваны реализацией, перечислены после соответствующей операции. В некоторых случаях реализация не способна различить возможные причины ошибки. В этом случае она должна вернуть isIllegalOperation.

Имеются три дополнительные функции для того, чтобы получить информацию о значении ошибки, --- это ioeGetHandle, которая возвращает Just hdl, если значение ошибки относится к дескриптору hdl, и Nothing иначе; ioeGetFileName, которая возвращает Just имя, если значение ошибки относится к файлу имя, и Nothing иначе; и ioeGetErrorString, которая возвращает строку. Для "пользовательских" ошибок (которые вызваны использованием fail), строка, возвращенная ioeGetErrorString, является аргументом, который был передан в fail; для всех остальных ошибок строка зависит от реализации.

Функция try возвращает ошибку в вычислении, явно использующем тип Either .

Функция bracket охватывает обычный способ выделения памяти, вычисления и освобождения памяти, в котором шаг освобождения должен произойти даже в случае ошибки во время вычисления. Это аналогично try-catch-finally в Java.

21.2  Файлы и дескрипторы

Haskell взаимодействует с внешнем миром через абстрактную файловую систему. Эта файловая система представляет собой совокупность именованных объектов файловой системы, которые можно организовать в каталоги (директории) (см. Directory). В некоторых реализациях каталоги могут сами являться объектами файловой системы и могут являться элементами в других каталогах. Для простоты любой объект файловой системы, который не является каталогом, называется файлом, хотя на самом деле это может быть канал связи или любой другой объект, распознаваемый операционной системой. Физические файлы --- это постоянные, упорядоченные файлы, которые обычно находятся на диске.

Имена файлов и каталогов являются значениями типа String, их точный смысл зависит от операционной системы. Файлы могут быть открыты; результатом открытия файла является дескриптор, который можно затем использовать для работы с содержимом этого файла.

Haskell определяет операции для чтения и записи символов соответственно из файла и в файл, они представлены значениями типа Handle. Каждое значение этого типа является дескриптором: записью, используемой системой поддержки выполнения программ Haskell для управления вводом - выводом объектов файловой системы. Дескриптор имеет по крайней мере следующие признаки:

Большинство дескрипторов будет также иметь текущую позицию ввода - вывода, указывающую где произойдет следующая операция ввода или вывода. Дескриптор является читаемым, если он управляет только вводом или и вводом, и выводом; аналогично, он является записываемым, если он управляет только выводом или и вводом, и выводом. Дескриптор является открытым, когда он впервые назначается. Как только он закрывается, его больше нельзя использовать ни для ввода, ни для вывода, хотя реализация не может повторно использовать его память, пока остаются ссылки на него. Дескрипторы находятся в классах Show и Eq. Строка, полученная в результате вывода дескриптора, зависит от системы; она должна включать достаточно информации, чтобы идентифицировать дескриптор для отладки. В соответствии с == дескриптор равен только самому себе; не делается никаких попыток сравнить внутреннее состояние различных дескрипторов на равенство.

21.2.1  Стандартные дескрипторы

Во время инициализации программы назначаются три дескриптора. Первые два (stdin и stdout) управляют вводом или выводом из стандартных каналов ввода или вывода программы на Haskell соответственно. Третий (stderr) управляет выводом в стандартный канал вывода ошибок. Эти дескрипторы первоначально открыты.

21.2.2  Полузакрытые дескрипторы

Операция hGetContents hdl (раздел 21.9.4) помещает дескриптор hdl в промежуточное состояние --- полузакрытое. В этом состоянии hdl фактически закрыт, но элементы читаются из hdl по требованию и накапливаются в специальном списке, возвращаемом hGetContents hdl.

Любая операция, которая завершается неуспешно из-за того, что дескриптор закрыт, также завершится неуспешно, если дескриптор полузакрыт. Единственное исключение --- hClose. Полузакрытый дескриптор становится закрытым, если:

Как только полузакрытый дескриптор становится закрытым, содержимое связанного с ним списка становится постоянным. Содержимое этого окончательного списка определено только частично: список будет содержать по крайней мере все элементы потока, которые были вычислены до того, как дескриптор стал закрытым.

Любые ошибки ввода - вывода, которые возникли в то время, когда дескриптор был полузакрыт, просто игнорируются.

21.2.3  Блокировка файлов

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

Предупреждение: операция readFile (раздел 7.1) хранит полузакрытый дескриптор файла до тех пор, пока все содержимое файла не будет считано. Из этого следует, что попытка записать в файл (используя writeFile, например), который было ранее открыт с помощью readFile, обычно завершается с ошибкой isAlreadyInUseError.

21.3  Открытие и закрытие файлов

21.3.1  Открытие файлов

Функция openFile файл режим назначает и возвращает новый, открытый дескриптор для управления файлом файл. Он управляет вводом, если режим равен ReadMode, выводом, если режим равен WriteMode или AppendMode, и вводом и выводом, если режим равен ReadWriteMode.

Если файл не существует и открывается для вывода, должен быть создан новый файл. Если режим равен WriteMode и файл уже существует, то файл должен быть усечен до нулевой длины. Некоторые операционные системы удаляют пустые файлы, поэтому нет гарантии, что файл будет существовать после openFile с режимом WriteMode, если он не будет впоследствии успешно записан. Дескриптор устанавливается в конец файла, если режим равен AppendMode, иначе --- в начало файла (в этом случае его внутренняя позиция ввода - вывода равна 0). Начальный режим буферизации зависит от реализации.

Если openFile завершается неуспешно для файла, открываемого для вывода, файл тем не менее может быть создан, если он уже не существует.

Сообщения об ошибках: функция openFile может завершиться с ошибкой isAlreadyInUseError, если файл уже открыт и не может быть повторно открыт; isDoesNotExistError, если файл не существует, или isPermissionError, если пользователь не имеет прав на открытие файла.

21.3.2  Закрытие файлов

Функция hClose hdl делает дескриптор hdl закрытым. До того как завершится вычисление функции, если hdl является записываемым, то его буфер сбрасывается на диск как при использовании hFlush. Выполнение hClose на дескрипторе, который уже был закрыт, не влечет никаких действий; такое выполнение не является ошибкой. Все остальные операции на закрытом дескрипторе будут завершаться с ошибкой. Если hClose завершается неуспешно по какой-либо причине, все дальнейшие операции (кроме hClose) на дескрипторе будут тем не менее завершаться неуспешно, как будто hdl был успешно закрыт.

21.4  Определение размера файла

Для дескриптора hdl, который прикреплен к физическому файлу, hFileSize hdl возвращает размер этого файла в 8-битных байтах (>= 0).

21.5  Обнаружение конца ввода

Для читаемого дескриптора hdl функция hIsEOF hdl возвращает True, если никакой дальнейший ввод не может быть получен из hdl; для дескриптора, прикрепленного к физическому файлу, это означает, что текущая позиция ввода - вывода равна длине файла. В противном случае функция возвращает False. Функция isEOF идентична описанной функции, за исключением того, что она работает только с stdin.

21.6  Операции буферизации

Поддерживаются три вида буферизации: буферизация строк, буферизация блоков или отсутствие буферизации. Эти режимы имеют следующие результаты. При выводе элементы записываются или сбрасываются на диск из внутреннего буфера в соответствии с режимом буферизации:

В реализации содержимое буфера может сбрасываться на диск более часто, но не менее часто, чем определено выше. Буфер опусташается, как только он записывается.

Аналогично, в соответствии с режимом буферизации для дескриптора hdl выполняется ввод:

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

Функция hSetBuffering hdl режим устанавливает режим буферизации для дескриптора hdl для последующих чтений и записей.

Если режим буферизации BlockBuffering или LineBuffering изменен на NoBuffering, тогда

Сообщения об ошибках: функция hSetBuffering может завершиться с ошибкой isPermissionError, если дескриптор уже использовался для чтения или записи и реализация не позволяет изменить режим буферизации.

Функция hGetBuffering hdl возвращает текущий режим буферизации для hdl.

Режим буферизации по умолчанию при открытии дескриптора зависит от реализации и может зависеть от объекта файловой системы, к которому прикреплен дескриптор.

21.6.1  Сбрасывание буферов на диск

Функция hFlush hdl заставляет любые элементы, буферизированные для вывода в дескрипторе hdl, тотчас же передать операционной системе.

Сообщения об ошибках: функция hFlush может завершиться с ошибкой isFullError, если устройство заполнено; isPermissionError, если превышены пределы системных ресурсов. При этих обстоятельствах не определено, будут ли символы в буфере проигнорированы или останутся в буфере.

21.7  Позиционирование дескрипторов

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

Функция hGetPosn hdl возвращает текущую позицию ввода - вывода hdl в виде значения абстрактного типа HandlePosn. Если вызов hGetPosn h возвращает позицию p, тогда функция hSetPosn p устанавливает позицию h в позицию, которую она содержала во время вызова hGetPosn.

Сообщения об ошибках: функция hSetPosn может завершиться с ошибкой isPermissionError, если были превышены пределы ресурсов системы.

21.7.2  Установка новой позиции

Функция hSeek hdl режим i устанавливает позицию дескриптора hdl в зависимости от режима. Если режим равен:

Смещение задается в 8-битных байтах.

Если для hdl используется режим буферизации строк или блоков, то установка позиции, которая не находится в текущем буфере, приведет к тому, что сначала будут записаны в устройство все элементы в выходном буфере, а затем входной буфер будет проигнорирован. Некоторые дескрипторы не могут использоваться для установки позиции (см. hIsSeekable) или могут поддерживать только подмножество возможных операций позиционирования (например, только возможность установить дескриптор в конец ленты или возможность сместить дескриптор в положительном направлении от начала файла или от текущей позиции). Невозможно установить отрицательную позицию ввода - вывода или для физического файла установить позицию ввода - вывода за пределы текущего конца файла.

Сообщения об ошибках: функция hSeek может завершиться с ошибкой isPermissionError, если были превышены пределы системных ресурсов.

21.8  Свойства дескрипторов

Функции hIsOpen, hIsClosed, hIsReadable, hIsWritable и hIsSeekable возвращают информацию о свойствах дескриптора. Каждый из возвращаемых результатов равен True, если дескриптор обладает указанным свойством, и False иначе.

21.9  Ввод и вывод текста

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

21.9.1  Проверка ввода

Функция hWaitForInput hdl t ждет, пока не станет доступным ввод по дескриптору hdl. Она возвращает True, как только станет доступным ввод для hdl, или False, если ввод не доступен в пределах t миллисекунд.

Функция hReady hdl указывает, есть ли по крайней мере один элемент, доступный для ввода из дескриптора hdl.

Сообщения об ошибках: функции hWaitForInput и hReady завершаются с ошибкой isEOFError, если был достигнут конец файла.

21.9.2  Чтение ввода

Функция hGetChar hdl считывает символ из файла или канала, управляемого hdl.

Функция hGetLine hdl считывает строку из файла или канала, управляемого hdl. getLine из Prelude является краткой записью для hGetLine stdin.

Сообщения об ошибках: функция hGetChar завершается с ошибкой isEOFError, если был достигнут конец файла. Функция hGetLine завершается с ошибкой isEOFError, если конец файла был обнаружен при считывании первого символа строки. Если hGetLine обнаружит конец файла в любом другом месте при считывании строки, он будет обработан как признак конца строки и будет возвращена (неполная) строка.

21.9.3  Считывание вперед

Функция hLookAhead hdl возвращает следующий символ из дескриптора hdl, не удаляя его из входного буфера; она блокируется до тех пор, пока символ не станет доступен.

Сообщения об ошибках: функция hLookAhead может завершиться с ошибкой isEOFError, если был достигнут конец файла.

21.9.4  Считывание всего ввода

Функция hGetContents hdl возвращает список символов, соответствующих непрочитанной части канала или файла, управляемого hdl, который сделан полузакрытым.

Сообщения об ошибках: функция hGetContents может завершиться с ошибкой isEOFError, если был достигнут конец файла.

21.9.5  Вывод текста

Функция hPutChar hdl c записывает символ c в файл или канал, управляемый hdl. Символы могут быть буферизированы, если включена буферизация для hdl.

Функция hPutStr hdl s записывает строку s в файл или канал, управляемый hdl.

Функция hPrint hdl t записывает строковое представление t, полученное функцией shows, в файл или канал, управляемый hdl, и добавляет в конец символ новой строки.

Сообщения об ошибках: функции hPutChar, hPutStr и hPrint могут завершиться с ошибкой isFullError, если устройство заполнено, или isPermissionError, если превышены пределы других системных ресурсов.

21.10  Примеры

Рассмотрим некоторые простые примеры, иллюстрирующие ввод - вывод в Haskell .

21.10.1  Суммирование двух чисел

Эта программа считывает и суммирует два числа типа Integer.

import IO

main = do
         hSetBuffering stdout NoBuffering            
         putStr   "Введите целое число: "        
         x1 <- readNum 
         putStr   "Введите другое целое число: "          
         x2 <- readNum                          
         putStr  ("Их сумма равна " ++ show (x1+x2) ++ "\n")
       where readNum :: IO Integer
-- Указание сигнатуры типа позволяет избежать 
-- исправления типов x1,x2 правилом по умолчанию 
             readNum = readLn

21.10.2  Копирование файлов

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

import IO
import System
import Char( toUpper )

main = do 
         [f1,f2] <- getArgs
         h1 <- openFile f1 ReadMode     
         h2 <- openFile f2 WriteMode 
         copyFile h1 h2            
         hClose h1                  
         hClose h2

copyFile h1 h2 = do
                   eof <- hIsEOF h1
                   if eof then return () else
                      do
                        c <- hGetChar h1
                        hPutChar h2 (toUpper c)   
                        copyFile h1 h2

Эквивалентная, но значительно более короткая версия, использующая ввод - вывод строк:

import System
import Char( toUpper )

main = do
         [f1,f2] <- getArgs
         s <- readFile f1
         writeFile f2 (map toUpper s)

21.11  Библиотека IO

module IO {- список экспорта пропущен -} where

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

try            :: IO a -> IO (Either IOError a)
try f          =  catch (do r <- f
                            return (Right r))
                        (return . Left)

bracket        :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket before after m = do
        x  <- before
        rs <- try (m x)
        after x
        case rs of
           Right r -> return r
           Left  e -> ioError e

-- вариант приведенного выше, где средняя функция не требует x
bracket_        :: IO a -> (a -> IO b) -> IO c -> IO c
bracket_ before after m = do
         x  <- before
         rs <- try m
         after x
         case rs of
            Right r -> return r
            Left  e -> ioError e


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