Россия, Петерубрг, СПБ-ГПУ, 1998 |
Ввод - вывод
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. Полузакрытый дескриптор становится закрытым, если:
- по отношению к нему применен 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. Операции буферизации
Поддерживаются три вида буферизации: буферизация строк, буферизация блоков или отсутствие буферизации. Эти режимы имеют следующие результаты. При выводе элементы записываются или сбрасываются на диск из внутреннего буфера в соответствии с режимом буферизации:
- буферизация строк: весь буфер сбрасывается на диск всякий раз, когда выводится символ новой строки, переполняется буфер, вызывается hFlush или закрывается дескриптор.
- буферизация блоков: весь буфер записывается всякий раз, когда он переполняется, вызывается hFlush или закрывается дескриптор.
- отсутствие буферизации: вывод записывается сразу и никогда не сохраняется в буфере.
В реализации содержимое буфера может сбрасываться на диск более часто, но не менее часто, чем определено выше. Буфер опусташается, как только он записывается.
Аналогично, в соответствии с режимом буферизации для дескриптора hdl выполняется ввод:
- буферизация строк: когда буфер для hdl не является пустым, следующий элемент получается из буфера; иначе, когда буфер пуст, символы считываются в буфер до тех пор, пока не встретится символ новой строки или буфер станет полон. Символы недоступны, пока не появится символ новой строки или буфер не станет полон.
- буферизация блоков: когда буфер для hdl становится пустым, следующий блок данных считывается в буфер.
- отсутствие буферизации: следующий элемент ввода считывается и возвращается. Операция hLookAhead (раздел "21.9.3" ) предполагает, что даже дескриптор в режиме отсутствия буферизации может потребовать буфер в один символ.
В большинстве реализаций для физических файлов обычно применяется буферизация блоков, а для терминалов обычно применяется буферизация строк.
Функция hSetBuffering hd l режим устанавливает режим буферизации для дескриптора hdl для последующих чтений и записей.
- Если режим равен LineBuffering, включается режим буферизации строк, если это возможно.
- Если режим равен BlockBuffering размер, включается режим буферизации блоков, если это возможно. Размер буфера равен n элементов, если размер равен Just n, а иначе зависит от реализации.
- Если режим равен NoBuffering, то режим буферизации отключается, если это возможно.
Если режим буферизации BlockBuffering или LineBuffering изменен на NoBuffering, тогда
- если hdl является записываемым, то буфер сбрасывается на диск как при hFlush ;
- если hdl не является записываемым, то содержимое буфера игнорируется.
Сообщения об ошибках: функция 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 в зависимости от режима. Если режим равен:
- AbsoluteSeek: позиция hdl устанавливается в i.
- RelativeSeek: позиция hdl смещается на i от текущей позиции.
- SeekFromEnd: позиция hdl смещается на i от конца файла.
Смещение задается в 8-битных байтах.
Если для hdl используется режим буферизации строк или блоков, то установка позиции, которая не находится в текущем буфере, приведет к тому, что сначала будут записаны в устройство все элементы в выходном буфере, а затем входной буфер будет проигнорирован. Некоторые дескрипторы не могут использоваться для установки позиции (см. hIsSeekable) или могут поддерживать только подмножество возможных операций позиционирования (например, только возможность установить дескриптор в конец ленты или возможность сместить дескриптор в положительном направлении от начала файла или от текущей позиции). Невозможно установить отрицательную позицию ввода - вывода или для физического файла установить позицию ввода - вывода за пределы текущего конца файла.
Сообщения об ошибках: функция hSeek может завершиться с ошибкой isPermissionError, если были превышены пределы системных ресурсов.
21.8. Свойства дескрипторов
Функции hIsOpen, hIsClosed, hIsReadable, hIsWritable и hIsSeekable возвращают информацию о свойствах дескриптора. Каждый из возвращаемых результатов равен True, если дескриптор обладает указанным свойством, и False иначе.