Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
864 lines (729 sloc) 79.4 KB
\chapter{Файлы и файловый ввод/вывод}
\label{ch:14}
\thispagestyle{empty}
Common Lisp предоставляет мощную библиотеку для работы с файлами. В~этой главе я
остановлюсь на нескольких элементарных задачах, относящихся к файловыми операциям: чтение,
запись, а также отображение структуры файловой системы. Средства Common Lisp,
предназначенные для ввода/вывода, аналогичны имеющимся в других языках
программирования. Common Lisp предоставляет потоковую абстракцию операций чтения/записи, а
для манипулирования файловыми объектами в независимом от операционной системы формате~---
абстракцию файловых путей. Кроме того, Common Lisp предоставляет некоторое
количество уникальных возможностей, такие как чтение и запись s-выражений.
\section{Чтение данных из файлов}
Самая фундаментальная задача ввода/вывода~--- чтение содержимого файла. Для того, чтобы
получить поток, из которого вы можете прочитать содержимое файла, используется функция
\lstinline{OPEN}. По умолчанию, \lstinline{OPEN} возвращает посимвольный поток ввода данных, который
можно передать множеству функций, считывающих один или несколько символов текста:
\lstinline{READ-CHAR} считывает одиночный символ; \lstinline{READ-LINE} считывает строку текста,
возвращая её как строку без символа конца строки; функция \lstinline{READ} считывает одиночное
s-выражение, возвращая объект Lisp. Когда работа с потоком завершена, вы можете закрыть
его с помощью функции \lstinline{CLOSE}.
Функция \lstinline{OPEN} требует имя файла как единственный обязательный аргумент. Как можно
увидеть в разделе~<<\nameref{ch14:file-names}>>, Common Lisp предоставляет два пути для представления
имени файла, но наиболее простой способ~--- использовать строку, содержащую имя в формате,
используемом в файловой системе. Так, предполагая, что \lstinline{/some/file/name.txt} это
файл, возможно открыть его следующим образом:
\begin{myverb}
(open "/some/file/name.txt")
\end{myverb}
Вы можете использовать объект, возвращаемый функцией, как первый аргумент любой функции,
осуществляющей чтение. Например, для того, чтобы напечатать первую строку файла, вы можете
комбинировать \lstinline{OPEN}, \lstinline{READ-LINE}, \lstinline{CLOSE} следующим образом:
\begin{myverb}
(let ((in (open "/some/file/name.txt")))
(format t "~a~%" (read-line in))
(close in))
\end{myverb}
Конечно, при открытии и чтении данных может произойти ряд ошибок. Файл может не
существовать, или вы можете непредвиденно достигнуть конца файла в процессе его чтения. По
умолчанию, \lstinline{OPEN} и \lstinline{READ-*} будут сигнализировать об ошибках в данных
ситуациях. В~главе~\ref{ch:19}, я рассмотрю как обработать эти ошибки. Сейчас же, однако, будем
использовать более легковесное решение: каждая из этих функций принимает аргументы,
которые изменяют её реакцию на исключительные ситуации.
Если вы хотите открыть файл, который возможно не существует, без генерации ошибки
функцией \lstinline{OPEN}, вы можете использовать аргумент \lstinline{:if-does-not-exists} для того,
чтобы указать другое поведение. Три различных значения допустимы для данного аргумента~---
\lstinline{:error}, по умолчанию; \lstinline{:create}, что указывает на необходимость создания файла
и повторное его открытие как существующего и \lstinline{NIL}, что означает возврат \lstinline{NIL}
(при неуспешном открытии) вместо потока. Итак, возможно изменить предыдущий пример таким
образом, чтобы обработать несуществующий файл.
\begin{myverb}
(let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
(when in
(format t "~a~%" (read-line in))
(close in)))
\end{myverb}
Все функции чтения~--- \lstinline{READ-CHAR}, \lstinline{READ-LINE}, \lstinline{READ}~--- принимают
опциональный аргумент, по умолчанию <<истина>>, который указывает должны ли они
сигнализировать об ошибке, если они достигли конца файла. Если этот аргумент установлен в
\lstinline{NIL}, то они возвращают значение их 3го аргумента, который по умолчанию \lstinline{NIL},
вместо ошибки. Таким образом, вывести на печать все строки файла можно следующим способом:
\begin{myverb}
(let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
(when in
(loop for line = (read-line in nil)
while line do (format t "~a~%" line))
(close in)))
\end{myverb}
Среди трёх функций чтения, \lstinline{READ}~--- уникальна. Это~--- та самая функция, которая
представляет букву <<R>> в <<REPL>>, и которая используется для того, чтобы читать исходный
код Lisp. Во время вызова она читает одиночное s-выражение, пропуская пробельные символы и
комментарии, и возвращает объект Lisp, представляемый s-выражением. Например, предположим,
что файл \lstinline{/some/file/name.txt} содержит следующие строки:
\begin{myverb}
(1 2 3)
456
"строка" ; это комментарий
((a b)
(c d))
\end{myverb}
Другими словами, он содержит 4 s-выражения: список чисел, число, строку, и список
списков. Вы можете считать эти выражения следующим образом:
\begin{myverb}
CL-USER> (defparameter *s* (open "/some/file/name.txt"))
*S*
CL-USER> (read *s*)
(1 2 3)
CL-USER> (read *s*)
456
CL-USER> (read *s*)
"строка"
CL-USER> (read *s*)
((A B) (C D))
CL-USER> (close *s*)
T
\end{myverb}
Как было показано в главе~\ref{ch:03}, возможно использовать \lstinline{PRINT} для того, чтобы выводить
объекты Lisp на печать в удобочитаемой форме. Итак, когда необходимо хранить данные в
файлах, \lstinline{PRINT} и \lstinline{READ} предоставляют простой способ делать это без создания
специального формата данных и парсера для их прочтения. Вы даже можете использовать
комментарии без ограничений. И, поскольку s-выражения создавались для того, чтобы быть
редактируемыми людьми, то они так же хорошо подходят для использования в качестве формата
конфигурационных файлов\footnote{Заметим, однако, что процедура чтения Lisp, зная как
пропускать комментарии, полностью их пропускает. Поэтому, если вы считаете
конфигурационный файл, содержащий комментарии, при помощи \lstinline{READ}, а затем запишете
изменения при помощи \lstinline{PRINT}, то потеряете все комментарии.}\hspace{\footnotenegspace}.
\section{Чтение двоичных данных}
По умолчанию \lstinline{OPEN} возвращает символьные потоки, которые преобразуют байты в символы
в соответствии с конкретной схемой кодирования символов\footnote{По умолчанию \lstinline{OPEN}
использует кодировку, используемую в операционной системе, но возможно указать ключевой
параметер \lstinline{:external-format}, в котором передать используемую схему кодирования,
отличную от используемой в операционной системе. Символьные потоки также преобразуют
платформозависимые символы конца строки в символ \lstinline!#\Newline!.}\hspace{\footnotenegspace}.
Для чтения потока байтов необходимо передать функции \lstinline{OPEN} ключевой параметр
\lstinline{:element-type} со значением \lstinline{'(unsigned-byte 8)}\footnote{Тип
\lstinline{(unsigned-byte 8)} обозначает 8-битный беззнаковый байт; <<Байты>> в Common Lisp
могут иметь различный размер поскольку Lisp может выполняться на различных платформах с
размерами байтов от 6 до 9 бит, к примеру PDP-10, может адресовать битовые поля
различной длины от 1 до 36 бит.}\hspace{\footnotenegspace}. Полученный поток можно передать функции
\lstinline{READ-BYTE}, которая будет возвращать целое число от 0 до 255 во время каждого
вызова. \lstinline{READ-BYTE}, так же, как и функции, работающие с потоками символов, принимает
опциональные аргументы, которые указывают, должна ли она сигнализировать об ошибке, если
достигнут конец файла, и какое значение возвращать в противном случае. В~главе~\ref{ch:24}
мы построим библиотеку, которая позволит удобно читать структурированные бинарные данные,
используя \lstinline{READ-BYTE}\footnote{В~общем, поток может быть либо символьным, либо
бинарным, так что невозможно смешивать вызовы \lstinline{READ-BYTE} с \lstinline{READ-CHAR} и
другими символьными функциями. Однако, в некоторых реализациях, таких как Allegro,
поддерживаются так называемые бивалентные потоки, которые поддерживают как символьные
так и байтовые операции ввода/вывода.}\hspace{\footnotenegspace}.
\section{Блочное чтение}
Последняя функция для чтения, \lstinline{READ-SEQUENCE}, работает с бинарными и символьными
потоками. Ей передаётся последовательность (обычно вектор) и поток, и она пытается
заполнить эту последовательность данными из потока. Функция возвращает индекс первого
элемента последовательности, который не был заполнен, либо её длину, если она была
заполнена полностью. Так же возможно передать ключевые аргументы \lstinline{:start} и
\lstinline{:end}, которые указывают на подпоследовательность, которая должна быть заполнена
вместо последовательности. Аргумент, определяющий последовательность должен быть типом,
который может хранить элементы, из которых состоит поток. Поскольку большинство
операционных систем поддерживают только одну какую-либо форму блочных операций
ввода/вывода, \lstinline{READ-SEQUENCE} скорее всего более эффективна чем чтение
последовательных данных несколькими вызовами \lstinline{READ-BYTE} или \lstinline{READ-CHAR}.
\section{Файловый вывод}
Для записи данных в файл необходим поток вывода, который можно получить вызовом функции
\lstinline{OPEN} с ключевым аргументом \lstinline{:direction} \lstinline{:output}. Когда файл открывается
для записи, \lstinline{OPEN} предполагает, что файл не должен существовать, и будет сообщать об
ошибке в противном случае. Однако, возможно изменить это поведение с помощью ключевого
аргумента \lstinline{:if-exists}. Передавая значение \lstinline{:supersede} можно вызвать замену
существующего файла. Значение :append позволяет осуществлять запись таким образом, что
новые данные будут помещены в конец файла, а значение \lstinline{:overwrite} возвращает поток,
который будет переписывать существующие данные с начала файла. Если же передать
\lstinline{NIL}, то \lstinline{OPEN} вернёт \lstinline{NIL} вместо потока, если файл уже
существует. Характерное использование \lstinline{OPEN} для вывода данных выглядит следующим
образом:
\begin{myverb}
(open "/some/file/name.txt" :direction :output :if-exists :supersede)
\end{myverb}
Common Lisp также предоставляет некоторые функции для записи данных: \lstinline{WRITE-CHAR}
пишет одиночный символ в поток. \lstinline{WRITE-LINE} пишет строку, за которой следует символ
конца строки, с учётом реализации для конкретной платформы. Другая функция,
\lstinline{WRITE-STRING} пишет строку, не добавляя символ конца строки. Две разные функции
могут использоваться для того чтобы вывести символ конца строки: \lstinline{TERPRI}~---
сокращение для "TERminate PRInt" (закончить печать) безусловно печатает символ конца
строки, а \lstinline{FRESH-LINE} печатает символ конца строки только в том случае, если текущая
позиция печати не совпадает с началом строки. \lstinline{FRESH-LINE} удобна в том случае, когда
желательно избежать паразитных пустых строк в текстовом выводе, генерируемом другими
последовательно вызываемыми функциями. Допустим, например, что есть одна функция, которая
генерирует вывод и после которой обязательно должен идти перенос строки и другая, которая
должна начинаться с новой строки. Но, предположим, что если функции вызываются
последовательно, то необходимо обеспечить отсутствие лишних пустых строк в их выводе. Если
в начале второй функции используется \lstinline{FRESH-LINE}, её вывод будет постоянно начинать
с новой строки, но если она вызывается непосредственно после первой функции, то не будет
выводиться лишний перевод строки.
Некоторые функции позволяют вывести данные Lisp в форме s-выражений: \lstinline{PRINT} печатает
s-выражение, предваряя его символом начала строки, и пробельным символом
после. \lstinline{PRIN1} печатает только s-выражение. А функция \lstinline{PPRINT} печатает
s-выражения аналогично \lstinline{PRINT} и \lstinline{PRIN1}, но использует <<красивую печать>>,
которая пытается печатать s-выражения в эстетически красивом виде.
Однако, не все объекты могут быть напечатаны в том формате, который понимает
\lstinline{READ}. Переменная \lstinline{*PRINT-READABLY*} контролирует поведение при попытке
напечатать подобный объект с помощью \lstinline{PRINT}, \lstinline{PRIN1} или \lstinline{PPRINT}. Когда
она равна \lstinline{NIL}, эти функции напечатают объект в таком формате, что \lstinline{READ} при
попытке чтения гарантировано сообщит об ошибке; в ином случае они просигнализируют об
ошибке вместо того, чтобы напечатать объект.
Еще одна функция, \lstinline{PRINC}, также печатает объекты Lisp, но в виде, удобном для
человеческого восприятия. Например, \lstinline{PRINC} печатает строки без кавычек. Текстовый
вывод может быть ещё более замысловатым, если задействовать потрясающе гибкую, и в
некоторой степени загадочную функцию \lstinline{FORMAT}. В~главе~\ref{ch:18} я расскажу о
некоторых важных тонкостях этой функции, которая, по сути, определяет мини-язык для
форматированного вывода.
Для того, чтобы записать двоичные данные в файл, следует открыть файл функцией \lstinline{OPEN}
с тем же самым аргументом \lstinline{:element-type}, который использовался при чтении данных:
\lstinline{'(unsigned-byte 8)}. После этого можно записывать в поток отдельные байты функцией
\lstinline{WRITE-BYTE}.
Функция блочного вывода \lstinline{WRITE-SEQUENCE} принимает как двоичные, так и символьные
потоки до тех пор, пока элементы последовательности имеют подходящий тип: символы или
байты. Так же как и \lstinline{READ-SEQUENCE}, эта функция наверняка более эффективна, чем
запись элементов последовательности поодиночке.
\section{Закрытие файлов}
Любой, кто писал программы, взаимодействующие с файлами, знает, что важно закрывать файлы,
когда работа с ними закончена, так как дескрипторы норовят быть дефицитным ресурсом. Если
открывают файлы и забывают их закрывать, вскоре обнаруживают, что больше нельзя открыть ни
одного файла\pclfootnote{Некоторые могут полагать, что это не является проблемой в языках со
сборщиком мусора, таких как Lisp. В~большинстве реализаций Lisp все потоки, которые
больше не нужны, автоматически закрываются. Но на это не надо полагаться, так как
сборщик мусора запускается, как правило, когда остаётся мало памяти. Он ничего не знает
о других дефицитных ресурсах таких, как файловые дескрипторы. Если доступно много
памяти, то доступные файловые дескрипторы могут быстро закончиться до вызова сборщика
мусора.}. На первый взгляд может показаться, что достаточно каждый вызов \lstinline{OPEN}
сопоставить с вызовом \lstinline{CLOSE}. Например, можно всегда обрамлять код, использующий
файл, как показано ниже:
\begin{myverb}
(let ((stream (open "/some/file/name.txt")))
;; работа с потоком
(close stream))
\end{myverb}
Однако этом метод имеет две проблемы. Первая~--- он предрасположен к ошибкам: если забыть
написать \lstinline{CLOSE}, то будет происходить утечка дескрипторов при каждом вызове этого
кода. Вторая~--- наиболее значительная~--- нет гарантии, что \lstinline{CLOSE} будет
достигнут. Например, если в коде, расположенном до \lstinline{CLOSE}, есть \lstinline{RETURN} или
\lstinline{RETURN-FROM}, возвращение из \lstinline{LET} произойдёт без закрытия потока. Или, как вы
увидите в главе~\ref{ch:19}, если какая-либо часть кода до \lstinline{CLOSE} сигнализирует об
ошибке, управление может перейти за пределы \lstinline{LET} обработчику ошибки и никогда не
вернётся, чтобы закрыть поток.
Common Lisp предоставляет общее решение того, как удостовериться, что определённый код
всегда исполняется: специальный оператор \lstinline{UNWIND-PROTECT}, о котором я расскажу в
главе~\ref{ch:20}. Так как открытие файла, работа с ним и последующее закрытие очень часто
употребляются, Common Lisp предлагает макрос, \lstinline{WITH-OPEN-FILE}, основанный на
\lstinline{UNWIND-PROTECT}, для скрытия этих действий. Ниже — основная форма:
\begin{myverb}
(with-open-file (stream-var open-argument*)
body-form*)
\end{myverb}
Выражения в \lstinline{body-form*} вычисляются с \lstinline{stream-var}, связанной с файловым
потоком, открытым вызовом \lstinline{OPEN} с аргументами
\lstinline{open-argument*}. \lstinline{WITH-OPEN-FILE} удостоверяется, что поток \lstinline{stream-var}
закрывается до того, как из \lstinline{WITH-OPEN-FILE} вернётся управление. Поэтому читать файл
можно следующим образом:
\begin{myverb}
(with-open-file (stream "/some/file/name.txt")
(format t "~a~%" (read-line stream)))
\end{myverb}
Создать файл можно так:
\begin{myverb}
(with-open-file (stream "/some/file/name.txt" :direction :output)
(format stream "Какой-то текст."))
\end{myverb}
Как правило, \lstinline{WITH-OPEN-FILE} используется в 90-99 процентах файлового
ввода/вывода. Вызовы \lstinline{OPEN} и \lstinline{CLOSE} понадобятся, если файл нужно открыть в
какой-либо функции и оставить поток открытым при возврате из неё. В~таком случае вы должны
позаботиться о закрытии потока самостоятельно, иначе произойдёт утечка файловых
дескрипторов и, в конце концов, вы больше не сможете открыть ни одного файла.
\section{Имена файлов}
\label{ch14:file-names}
До сих пор мы использовали строки для представления имён файлов. Однако, использование
строк как имён файлов привязывает код к конкретной операционной и файловой системам. Точно
так же, если конструировать имена в соответствии правилам конкретной схемы именования
(скажем, разделение директорий знаком <</>>), то вы также привязаны к одной определённой
файловой системе.
Для того, чтобы избежать подобной непереносимости программ, Common Lisp предоставляет
другое представление файловых имён: объекты файловых путей. Файловые пути представляют
собой файловые имена в структурированном виде, что делает их использование лёгким без
привязки к определённому синтаксису файловых имён. А бремя по переводу между строками в
локальном представлении~--- строками имён~--- и файловыми путями ложится на плечи реализаций
Lisp.
К несчастью, как и со многими абстракциями, спроектированными для скрытия деталей
различных базовых систем, абстракция файловых путей привносит свои трудности. В~то время,
когда разрабатывались файловые пути, разнообразие широко используемых файловых систем было
чуть значительнее, чем сегодня. Но это мало проясняет ситуацию, если все, о чем вы
заботитесь,~--- представление имён файлов в Unix или Windows. Однако однажды поняв какие
части этой абстракции можно забыть, как артефакты истории развития файловых путей, вы
сможете ловко управлять именами файлов\pclfootnote{Еще одна причина, по которой система
файловых путей выглядит причудливо,~--- введение логических файловых путей. Однако, вы
можете плодотворно использовать систему файловых путей с единственной мыслью в голове о
логических файловых путях: о них можно не вспоминать. В~двух словах, логические файловые
пути позволяют программам, написанных на Common Lisp, ссылаться на файловые пути без
именования конкретного файла. Они могут быть отображены впоследствии на конкретную точку
файловой системы, когда будет установлена ваша программа, при помощи <<преобразования
логических файловых путей>>, которое преобразует эти имена, соответствующие определённому
образцу, в файловые пути, представляющие файлы в файловой системе, так называемые
физические файловые пути. Они находят применение в определённых ситуациях, но вы может
со спокойной душой пройти мимо них.}.
\vspace{1cm}
\textintable{Как мы до этого докатились?}{Историческое разнообразие файловых систем, существующих в период 70-80 годов, можно легко
забыть. Кент Питман, один из ведущих технических редакторов стандарта Common Lisp, описал
однажды ситуацию в comp.lang.lisp (Message-ID: \lstinline{sfwzo74np6w.fsf@world.std.com}) так:
В~момент завершения проектирования Common Lisp господствующими файловыми системами были
\lstinline{TOPS-10}, \lstinline{TENEX}, \lstinline{TOPS-20}, \lstinline{VAX VMS}, \lstinline{AT\&T Unix}, \lstinline{MIT
Multics}, \lstinline{MIT ITS}, и это не упоминаю группу систем для мэйнфрэймов. В~некоторых
системах имена файлов были только в верхнем регистре, в других~--- смешанные, в третьих~---
чувствительны к регистру, но с возможностью преобразования (как в CL). Какие-то имели
групповые символы (wildcards), какие-то~--- нет. Одни имели \lstinline{:вверх} (\lstinline{:up}) в
относительных файловых путях, другие~--- нет. Также существовали файловые системы без
каталогов, файловые системы без иерархической структуры каталогов, файловые системы без
типов файлов, файловые системы без версий, файловые системы без устройств и т.д.
Если сейчас посмотреть на абстракцию файловых путей с точки зрения какой-нибудь
определённой файловой системы, она выглядит нелепо. Но если взять в рассмотрение даже
такие две похожие файловые системы, как в Windows и Unix, то вы можете заметить отличия,
от которых можно отвлечься с помощью системы файловых путей. Файловые имена в Windows
содержат букву диска в то время, как в Unix нет. Другое преимущество владения абстракцией
файловых путей, которая спроектирована, чтобы оперировать большим разнообразием файловых
систем, которые существовали в прошлом,~--- её вероятная способность управлять файловыми
системами, которые будут существовать в будущем. Если, скажем, файловые системы с
сохранением всех старых данных и истории операций войдут снова в моду, Common Lisp будет к
этому готов.}
Как правило, когда возникает необходимость в файловом имени, вы можете использовать как
строку имени (namestring), так и файловый путь. Выбор зависит от того, откуда произошло
имя. Файловые имена, предоставленные пользователем~--- например, как аргументы или строки
конфигурационного файла~--- как правило, будут строками имён, так как пользователь знает
какая операционная система у него запущена, поэтому не следует ожидать, что он будет
беспокоиться о представлении файловых имён в Lisp. Но следуя общепринятой практике,
файловые имена будут представлены файловыми путями, так как они переносимы. Поток, который
возвращает \lstinline{OPEN}, также представляет файловое имя, а именно, файловое имя, которое
было изначально использовано для открытия этого потока. Вместе эти три типа упоминаются
как указатели файловых путей. Все встроенные функции, ожидающие файловое имя как аргумент,
принимают все три типа указателя файловых путей. Например, во всех предыдущих случаях,
когда вы использовали строку для представления файлового имени, вы также могли передавать
в функцию объект файлового пути или поток.
\section{Как имена путей представляют имена файлов}
Файловый путь~--- это структурированный объект, который представляет файловое имя,
используя шесть компонентов: хост, устройство, каталог, имя, тип и версия. Большинство из
них принимают атомарные значения, как правило, строки; только директория~--- структурный
компонент, содержащий список имён каталогов (как строки) с предшествующим ключевым словом:
\lstinline{:absolute} (абсолютный) или \lstinline{:relative} (относительный). Но не все компоненты
необходимы на все платформах~--- это одна из тех вещей, которая вселяет страх в начинающих
лисперов, потому что необоснованно сложна. С другой стороны, вам не надо заботиться о том,
какие компоненты могут или нет использоваться для представления имён на определённой
файловой системе, если только вам не надо создать объект файлового пути с нуля, а это
почти никогда и не надо. Взамен Вы обычно получите объект файлового пути либо позволив
реализации преобразовать строку имени специфичной для какой-то файловой системы в объект
файлового пути, либо создав файловый путь, который перенимает большинство компонент от
какого-либо существующего.
Например, для того, чтобы преобразовать строку имени в файловый путь, используйте функцию
\lstinline{PATHNAME}. Она принимает на вход указатель файлового пути и возвращает эквивалентный
объект файлового пути. Если указатель уже является файловым путём, то он просто
возвращается. Если это поток, извлекается первоначальное файловое имя и возвращается. Если
это строка имени, она анализируется согласно локальному синтаксису файловых имён. Стандарт
языка как независимый от платформы документ не определяет какое-либо конкретное
отображение строки имени в файловый путь, но большинство реализаций следуют одним и тем же
соглашениям на данной операционной системе.
На файловых системах Unix, обычно, используются компоненты: директория, имя и тип. В
Windows на один компонент больше~--- обычно устройство или хост~--- содержит букву диска. На
этих платформах строку имени делят на части, а разделителем служит косая черта в Unix и
косая или обратная косая черты в Windows. Букву диска в Windows размещают либо в компонент
устройства или компонент хост. Все, кроме последнего из оставшихся элементов имени,
размещаются в списке, начинающемся с \lstinline{:absolute} или \lstinline{:relative} в зависимости
от того, начинается ли имя с разделителя или нет (игнорирую букву диска, если таковая
присутствует). Список становится компонентом каталог файлового пути. Последний элемент
делится по самой крайней точке, если она есть, и полученные две части есть компоненты имя
и тип, соответственно\footnote{Многие реализации Common Lisp под Unix трактуют файловые
имена, чей последний элемент начинается с точки и не содержит больше других точек,
следующим образом: помещают весь элемент~--- вместе с точкой~--- в компонент имя и
оставляют компонент тип равным \lstinline{NIL}.
\begin{myverb}
(pathname-name (pathname "/foo/.emacs")) -> ".emacs"
(pathname-type (pathname "/foo/.emacs")) -> NIL
\end{myverb}
Однако, не все реализации следуют этому соглашению. Некоторые создают файловый путь с
пустой строкой в качестве имени и emacs в качестве типа.}\hspace{\footnotenegspace}.
Можно проверить каждый компонент файлового пути с функциями \lstinline{PATHNAME-DIRECTORY},
\lstinline{PATHNAME-NAME} и \lstinline{PATHNAME-TYPE}.
\begin{myverb}
(pathname-directory (pathname "/foo/bar/baz.txt")) -> (:ABSOLUTE "foo" "bar")
(pathname-name (pathname "/foo/bar/baz.txt")) -> "baz"
(pathname-type (pathname "/foo/bar/baz.txt")) -> "txt"
\end{myverb}
Другие три функции~--- \lstinline{PATHNAME-HOST}, \lstinline{PATHNAME-DEVICE} и
\lstinline{PATHNAME-VERSION}~--- позволяют получить остальные три составляющие файлового пути,
хотя они и не представляют интереса в Unix. В~Windows либо \lstinline{PATHNAME-HOST}, либо
\lstinline{PATHNAME-DEVICE} возвратит букву диска.
Подобно другим встроенным объектам, файловые пути обладают своим синтаксисом для чтения:
\lstinline!#p!, за которым следует строка, заключенная в двойные кавычки. Это позволяет
печатать и считывать s-выражения, содержащие объекты файлового пути, но так как синтаксис
зависит от алгоритма анализа строки, эти данные могут быть непереносимыми между разными
операционными системами.
\begin{myverb}
(pathname "/foo/bar/baz.txt") -> #p"/foo/bar/baz.txt"
\end{myverb}
Для того, чтобы файловый путь преобразовать обратно в строку имени~--- например, чтобы
представить его пользователю~--- следует воспользоваться функцией \lstinline{NAMESTRING},
которая принимает указатель файлового пути и возвращает строку имени. Две других
функции~--- \lstinline{DIRECTORY-NAMESTRING} и \lstinline{FILE-NAMESTRING}~--- возвращают часть
строки имени. \lstinline{DIRECTORY-NAMESTRING} соединяет элементы компонента каталог в
локальное имя каталога. \lstinline{FILE-NAMESTRING}~--- компоненты имя и тип\footnote{Имя,
возвращённое функцией \lstinline{FILE-NAMESTRING}, также включает компонент версия на
файловой системе, которая использует это.}\hspace{\footnotenegspace}.
\begin{myverb}
(namestring #p"/foo/bar/baz.txt") -> "/foo/bar/baz.txt"
(directory-namestring #p"/foo/bar/baz.txt") -> "/foo/bar/"
(file-namestring #p"/foo/bar/baz.txt") -> "baz.txt"
\end{myverb}
\section{Конструирование имён путей}
Вы можете создать файловый путь, используя функцию \lstinline{MAKE-PATHNAME}. Она принимает по
одному аргументу-ключу на каждую компоненту файлового пути и возвращает файловый путь,
заполненный всеми предоставленными компонентами\footnote{Хост не может быть равным
\lstinline{NIL}, но если все же это так, то он будет заполнен значением, определённым
конкретной реализаций.}\hspace{\footnotenegspace}.
\begin{myverb}
(make-pathname
:directory '(:absolute "foo" "bar")
:name "baz"
:type "txt") -> #p"/foo/bar/baz.txt"
\end{myverb}
Однако, если вы желаете, чтобы ваши программы были переносимыми, то вряд ли вы пожелаете
создавать файловые пути с нуля, даже если абстракция файловых путей предохраняет вас от
непереносимого синтаксиса файловых имён, ведь файловые имена могут быть непереносимыми ещё
множеством способов. Например, файловое имя \lstinline{"/home/peter/foo.txt"} не очень-то
подходит для OS X, в которой \lstinline{/home/} представлено \lstinline{/Users/}.
Другой причиной, по которой не следует создавать файловые пути с нуля, является тот факт,
что различные реализации используют компоненты файлового пути с небольшими
различиями. Например, как было упомянуто выше, некоторые Windows-реализации LISP хранят
букву диска в компоненте устройство в то время, как другие~--- в компоненте хост. Если вы
напишите:
\begin{myverb}
(make-pathname :device "c" :directory '(:absolute "foo" "bar") :name "baz")
\end{myverb}
то это может быть правильным при использовании одной реализации, но не другой.
Вместо того, чтобы создавать пути с нуля, проще создать новый файловый путь, используя
существующий файловый путь, при помощи аргумента-ключа :defaults функции
\lstinline{MAKE-PATHNAME}. С этим параметром можно предоставить указатель файлового пути, из
которого будут взяты компоненты, не указанные другими аргументами. Для примера, следующее
выражение создаёт файловый путь с расширением \lstinline{.html} и компонентами из файлового
пути \lstinline{input-file}:
\begin{myverb}
(make-pathname :type "html" :defaults input-file)
\end{myverb}
Предполагая, что значение \lstinline{input-file} было предоставлено пользователем, этот код~---
надёжен вопреки различиям операционных систем и реализаций, таким как наличие либо
отсутствие в файловом пути буквы диска или место её расположения\footnote{Для
максимальной переносимости, следует писать:
\begin{myverb}
(make-pathname :type "html" :version :newest :defaults input-file)
\end{myverb}
Без аргумента :version на файловой системе с контролем версий, результирующий файловый
путь унаследует номер версии от входного файла, который, вероятнее всего, будет
неправильным, ведь если файл сохранялся не раз, он будет иметь больший номер, чем
созданный \lstinline{HTML} файл. В~реализациях без поддержки контроля версий, аргумент
\lstinline{:version} должен игнорироваться. Забота о переносимости~--- на вашей совести.}\hspace{\footnotenegspace}.
Используя эту же технику, можно создать файловый путь с другой компонентой директория.
\begin{myverb}
(make-pathname :directory '(:relative "backups") :defaults input-file)
\end{myverb}
Однако этот код создаст файловый путь с компонентой директория, равной относительному пути
\lstinline!backups/!, безотносительно к любым другим компонентам файла \lstinline!input-file!.
Например:
\begin{myverb}
(make-pathname :directory '(:relative "backups")
:defaults #p"/foo/bar/baz.txt") -> #p"backups/baz.txt"
\end{myverb}
Возможно, когда-нибудь вы захотите объединить два файловых пути, один из которых имеет
относительный компонент директория, путём комбинирования их компонент
директория. Например, предположим, что имеется относительный файловый путь
\lstinline!#p"foo/bar.html"!, который вы хотите объединить с абсолютным файловым путём
\lstinline!#p"/www/html/"!, чтобы получить \lstinline!#p"/www/html/foo/bar.html"!. В~этом
случае \lstinline{MAKE-PATHNAME} не подойдёт; то, что вам надо,~--- \lstinline{MERGE-PATHNAMES}.
\lstinline{MERGE-PATHNAMES} принимает два файловых пути и соединяет их, заполняя при этом
компоненты, которые в первом файловом пути равны \lstinline{NIL}, соответствующими значениями
из второго файлового пути. Это очень похоже на \lstinline{MAKE-PATHNAME}, которая заполняет все
неопределённые компоненты значениями, предоставленными аргументом
\lstinline{:defaults}. Однако, \lstinline{MERGE-PATHNAMES} особенно относится к компоненте
директория: если директория первого файлового пути~--- относительна, то директорией
окончательного файлового пути будет директория первого пути относительно директории
второго. Так:
\begin{myverb}
(merge-pathnames #p"foo/bar.html" #p"/www/html/") -> #p"/www/html/foo/bar.html"
\end{myverb}
Второй файловый путь также может быть относительным. В~этом случае окончательный путь
также будет относительным.
\begin{myverb}
(merge-pathnames #p"foo/bar.html" #p"html/") -> #p"html/foo/bar.html"
\end{myverb}
Для того, чтобы обратить это процесс, то есть получить файловый путь, который относителен
определённой корневой директории, используйте полезную функцию \lstinline{ENOUGH-NAMESTRING}.
\begin{myverb}
(enough-namestring #p"/www/html/foo/bar.html" #p"/www/") -> "html/foo/bar.html"
\end{myverb}
Вы можете соединить \lstinline{ENOUGH-NAMESTRING} и \lstinline{MERGE-PATHNAMES} для того, чтобы
создать файловый путь (с одинаковой относительной частью) в другой корневой директории.
\begin{myverb}
(merge-pathnames
(enough-namestring #p"/www/html/foo/bar/baz.html" #p"/www/")
#p"/www-backups/") -> #p"/www-backups/html/foo/bar/baz.html"
\end{myverb}
\lstinline{MERGE-PATHNAMES} используется стандартными функциями для доступа к файлам, чтобы
дополнять незавершённые файловые пути. Например, пусть есть файловый путь, имеющий только
компоненты имя и тип.
\begin{myverb}
(make-pathname :name "foo" :type "txt") -> #p"foo.txt"
\end{myverb}
Если вы попытаетесь передать этот файловый путь как аргумент функции \lstinline{OPEN},
недостающие компоненты, как, например, директория, должны быть заполнены, чтобы Lisp смог
преобразовать файловый путь в действительное файловое имя. Common Lisp добудет эти
значения для недостающих компонент, объединяя данный файловый путь со значением переменной
\lstinline{*DEFAULT-PATHNAME-DEFAULTS*}. Начальное значение этой переменной определено
реализацией, но, как правило, это файловый путь, компонент директория которого
представляет ту директорию, в которой Lisp был запущен. Компоненты хост и устройство
заполнены подходящими значениями, если в этом есть необходимость. Если
\lstinline{MERGE-PATHNAMES} вызвана только с одним аргументом, то она объединит аргумент со
значением \lstinline{*DEFAULT-PATHNAME-DEFAULTS*}. Например, если
\lstinline{*DEFAULT-PATHNAME-DEFAULTS*}~--- \lstinline!#p"/home/peter/"!, то в результате:
\begin{myverb}
(merge-pathnames #p"foo.txt") -> #p"/home/peter/foo.txt"
\end{myverb}
\section{Два представления для имён директорий}
Существует один неприятный момент при работе с файловым путём, который представляет
директорию. Файловые объекты разделяют компоненты директория и имя файла, но Unix и
Windows рассматривают директории как ещё один тип файла. Поэтому, в этих системах, каждая
директория может иметь два различных преставления.
Одно из них, которое я назову \lstinline{представлением файла}, рассматривает директорию, как
любой другой файл и размещает последний элемент строки имени в компоненты имя и
тип. Другое представление~--- представление директории~--- помещает все элементы имени в
компонент директория, оставляя компоненты имя и тип равными \lstinline{NIL}. Если
\lstinline{/foo/bar/}~--- директория, тогда любой из следующих двух файловых путей представляет
ее.
\begin{myverb}
(make-pathname :directory '(:absolute "foo") :name "bar") ; file form
(make-pathname :directory '(:absolute "foo" "bar")) ; directory form
\end{myverb}
Когда вы создаёте файловые пути с помощью \lstinline{MAKE-PATHNAME}, вы можете получить любое
из двух представлений, но нужно быть осторожным, когда имеете дело со строками имён. Все
современные реализации Lisp создают представление файла, если только строка имени не
заканчивается разделителем пути. Но вы не можете полагаться на то, что строки имени,
предоставленные пользователем, будут в том либо ином представлении. Например, предположим,
что вы запросили у пользователя имя директории, в которой сохраните файл. Пользователь
ввёл \lstinline!"/home/peter"!. Если передать функции \lstinline{MAKE-PATHNAME} эту строку как
аргумент \lstinline{:defaults}:
\begin{myverb}
(make-pathname :name "foo" :type "txt" :defaults user-supplied-name)
\end{myverb}
то в конце концов вы сохраните файл как \lstinline{/home/foo.txt}, а не
\lstinline{/home/peter/foo.text}, как предполагалось, так как \lstinline{"peter"} из строки имени
будет помещён в компонент имя, когда \lstinline{user-supplied-name} будет преобразовано в
файловый путь. В~переносимой библиотеке файловых путей, которую я обсужу в следующей
главе, вы напишите функцию pathname-as-directory, которая преобразует файловый объект в
представление директории. С этой функцией вы сможете сохранять наверняка файл в
директории, указанной пользователем.
\begin{myverb}
(make-pathname
:name "foo" :type "txt" :defaults (pathname-as-directory user-supplied-name))
\end{myverb}
\section{Взаимодействие с файловой системой}
Хоть открытие файлов для чтения и записи~--- наиболее частый вид взаимодействия с файловой
системой, порой вам захочется проверить существует ли файл, прочитать содержимое
директории, удалить или переименовать файлы, создать директории или получить такую
информацию о файле, как: кто его владелец, когда он последний раз был изменён, его
длину. Тут-то общность абстракции файловых путей немного некстати. Стандарт языка не
определяет, как функции, взаимодействующие с файловой системой, отображаются на какую-то
конкретную файловую систему, поэтому у создателей конкретных реализаций есть некоторая
свобода действий.
Несмотря на это большинство функций для взаимодействия с файловой системой просты для
понимания. Я расскажу о стандартных функциях и укажу на те, с которыми могут возникнуть
проблемы с переносимостью кода между реализациями. В~следующей главе вы разработаете
переносимую библиотеку файловых путей для того, чтобы сгладить эти проблемы с
переносимостью.
Для того, чтобы проверить существует ли файл, соответствующий указателю файлового пути~---
будь-то файловый путь, строка имени или файловый поток,~--- можно использовать функцию
\lstinline{PROBE-FILE}. Если файл, соответствующий указателю, существует, \lstinline{PROBE-FILE}
вернёт <<настоящее>> имя файла~--- файловый путь с любыми преобразованиями уровня файловой
системой, как, например, следование по символическим ссылкам. В~ином случае, она вернёт
\lstinline{NIL}. Однако, не все реализации позволяют использовать её для того, чтобы проверить
существует ли директория. Также, Common Lisp не предоставляет переносимого способа
определить, чем является существующий файл~--- обычным файлом или директорией. В~следующей
главе вы сделаете функцию-обёртку для \lstinline{PROBE-FILE}~--- \lstinline{file-exists-p}, которая
может проверить существует ли файл и ответить: данное имя является именем файла или
директории.
Так же стандартная функция для чтения списка файлов~--- \lstinline{DIRECTORY}~--- работает
прекрасно в незамысловатых случаях, но из-за различий реализаций её использование
становится мудреным делом. В~следующей главе вы определите функцию чтения содержимого
директорий, которая сгладит некоторые из различий.
\lstinline{DELETE-FILE} и \lstinline{RENAME-FILE} делают то, что следует из их
названий. \lstinline{DELETE-FILE} принимает указатель файлового пути и удаляет указанный файл,
возвращая истину в случае успеха. В~ином случае она сигнализирует
\lstinline{FILE-ERROR}\footnote{См. главу~\ref{ch:19} насчёт обработки ошибок.}\hspace{\footnotenegspace}.
\lstinline{RENAME-FILE} принимает два указателя файлового пути и изменяет имя файла, указанного
первым параметром, на имя, указанное вторым параметром.
Вы можете создать директории с помощью функции \lstinline{ENSURE-DIRECTORIES-EXIST}. Она
принимает указатель файлового пути и удостоверяется, что все элементы компонента
директория существуют и являются директориями, создавая их при необходимости. Так как она
возвращает файловый путь, который ей был передан, то удобно её вызывать на месте аргумента
другой функции.
\begin{myverb}
(with-open-file (out (ensure-directories-exist name) :direction :output)
...
)
\end{myverb}
Обратите внимание, что если вы передаёте \lstinline{ENSURE-DIRECTORIES-EXIST} имя директории,
то оно должно быть в представлении директории, или последняя директория не будет
создана. Обе функции \lstinline{FILE-WRITE-DATE} и \lstinline{FILE-AUTHOR} принимают указатель
файлового пути. \lstinline{FILE-WRITE-DATE} возвращает количество секунд, которое прошло с
полуночи 1-го января 1900 года, среднее время по Гринвичу (GMT), до времени последней
записи в файл. \lstinline{FILE-AUTHOR} возвращает~--- в Unix и Windows~--- владельца
файла\pclfootnote{Для приложений, которым нужен доступ к другим атрибутам файла в
определённой операционной системе или на файловой системе, некоторые библиотеки
предоставляют обвязки (bindings) для системных вызовов. Библиотека Osicat, размещённая
по адресу \url{http://common-lisp.net/project/osicat/}, предоставляет простой API,
созданный на основе Universal Foreign Function Interface (UFFI). Она должна работать с
большинством реализаций Common Lisp на POSIX-совместимых операционных системах.}.
Для того, чтобы получить размер файла, используйте функцию \lstinline{FILE-LENGTH}. По
историческим причинам \lstinline{FILE-LENGTH} принимает поток как аргумент, а не файловый
путь. В~теории это позволяет \lstinline{FILE-LENGTH} вернуть размер в терминах типа элементов
потока. Однако, так как на большинстве современных операционных системах единственная
доступная информация о размере файла~--- исключая чтение всего файла, чтобы узнать
размер~--- это размер в байтах, что возвращают большинство реализаций, даже если
\lstinline{FILE-LENGTH} передан символьный поток. Однако, стандарт не требует такого поведения,
поэтому ради предсказуемых результатов лучший способ получить размер файла~---
использовать бинарный поток\footnote{Количество байтов и символов в файле может
разниться, даже если не используется многобайтная кодировка. Потому что символьные
потоки также преобразуют специфичные для платформы переносы строк в единственный символ
\lstinline!#\Newline!. В~Windows, в которой используется \lstinline{CRLF} в качестве переноса
строки, количество символов, как правило, будет меньше чем количество байт. Если вам
действительно требуется знать количество символов в файле, то вы должны набраться
смелости и написать что-то похоже на:
\begin{myverb}
(with-open-file (in filename)
(loop while (read-char in nil) count t))
\end{myverb}
\noindent{}или, возможно, что-нибудь более эффективное, вроде этого:
\begin{myverb}
(with-open-file (in filename)
(let ((scratch (make-string 4096)))
(loop for read = (read-sequence scratch in)
while (plusp read) sum read)))
\end{myverb}
}\hspace{\footnotenegspace}.
\begin{myverb}
(with-open-file (in filename :element-type '(unsigned-byte 8))
(file-length in))
\end{myverb}
Похожая функция, которая также принимает открытый файловый поток в качестве аргумента~---
\lstinline{FILE-POSITION}. Когда ей передают только поток, она возвращает текущую позицию в
файле~--- количество элементов, прочитанных из потока или записанных в него. Когда её
вызывают с двумя аргументами, потоком и указателем позиции, она устанавливает текущей
позицией указанную. Указатель позиции должен быть ключевым словом \lstinline{:start},
\lstinline{:end} или неотрицательным целым числом. Два ключевых слова устанавливают позицию
потока в начало или конец. Если же передать функции целое число, то позиция переместится в
указанную позицию файла. В~случае бинарного потока позиция~--- это просто смещение в байтах
от начала файла. Однако символьные потоки немного сложнее из-за проблем с
кодировками. Лучшее что вы можете сделать если нужно переместиться на другую позицию в
текстовом файле~--- всегда передавать \lstinline{FILE-POSITION} в качестве второго аргумента
только то значение, которое вернула функция \lstinline{FILE-POSITION}, вызванная с тем же
потоком в качестве единственного аргумента.
\section{Другие операции ввода/вывода}
Вдобавок к файловым потокам Common Lisp поддерживает другие типы потоков, которые также
можно использовать с разнообразными функциями ввода/вывода для чтения, записи и
печати. Например, можно считывать данные из строки или записывать их в строку, используя
\lstinline{STRING-STREAM}, которые вы можете создать функциями \lstinline{MAKE-STRING-INPUT-STREAM}
и \lstinline{MAKE-STRING-OUTPUT-STREAM}.
\lstinline{MAKE-STRING-INPUT-STREAM} принимает строку и необязательные начальный и конечный
индексы, указывающие часть строки, которую следует связать с потоком, и возвращает
символьный поток, который можно передать как аргумент любой функции символьного ввода,
как, например, \lstinline{READ-CHAR}, \lstinline{READ-LINE} или \lstinline{READ}. Например, если у вас
есть строка, содержащая число с плавающей точкой с синтаксисом Common Lisp, вы можете
преобразовать её в число с плавающей точкой:
\begin{myverb}
(let ((s (make-string-input-stream "1.23")))
(unwind-protect (read s)
(close s)))
\end{myverb}
\lstinline{MAKE-STRING-OUTPUT-STREAM} создаёт поток, который можно использовать с
\lstinline{FORMAT}, \lstinline{PRINT}, \lstinline{WRITE-CHAR}, \lstinline{WRITE-LINE} и т.д. Она не принимает
аргументов. Что бы вы не записывали, строковый поток вывода будет накапливать это в
строке, которую потом можно получить с помощью функции
\lstinline{GET-OUTPUT-STREAM-STRING}. Каждый раз при вызове \lstinline{GET-OUTPUT-STREAM-STRING}
внутренняя строка потока очищается, поэтому существующий строковый поток вывода можно
снова использовать.
Однако, использовать эти функции напрямую вы будете редко, так как макросы
\lstinline{WITH-INPUT-FROM-STRING} и \lstinline{WITH-OUTPUT-TO-STRING} предоставляют более удобный
интерфейс. \lstinline{WITH-INPUT-FROM-STRING} похожа на \lstinline{WITH-OPEN-FILE}~--- она создаёт
строковый поток ввода на основе переданной строки и выполняет код в своём теле с потоком,
который присвоен переменной, вами предоставленной. Например, вместо формы \lstinline{LET} с
явным использованием \lstinline{UNWIND-PROTECT}, вероятно, лучше написать:
\begin{myverb}
(with-input-from-string (s "1.23")
(read s))
\end{myverb}
Макрос \lstinline{WITH-OUTPUT-TO-STRING} также связывает вновь созданный строковый поток вывода
с переменной, вами названной, и затем выполняет код в своём теле. После того, как код был
выполнен, \lstinline{WITH-OUTPUT-TO-STRING} вернёт значение, которое было бы возвращено
\lstinline{GET-OUTPUT-STREAM-STRING}.
\begin{myverb}
CL-USER> (with-output-to-string (out)
(format out "hello, world ")
(format out "~s" (list 1 2 3)))
"hello, world (1 2 3)"
\end{myverb}
Другие типы потоков, определённые в стандарте языка, предоставляют различные способы
<<соединения>> потоков, то есть позволяют подключать потоки друг к другу почти в любой
конфигурации. \lstinline{BROADCAST-STREAM}~--- поток вывода, который посылает записанные данные
множеству потоков вывода, переданных как аргументы функции-конструктору
\lstinline{MAKE-BROADCAST-STREAM}\footnote{\lstinline{MAKE-BROADCAST-STREAM} может создать <<чёрную
дыру>> для данных, если её вызвать без аргументов.}\hspace{\footnotenegspace}. В~противоположность этому,
\lstinline{CONCATENATED-STREAM}~--- поток ввода, который принимает ввод от множества потоков
ввода, перемещаясь от потока к потоку, когда очередной поток достигает конца. Потоки
\lstinline{CONCATENATED-STREAM} создаются функцией \lstinline{MAKE-CONCATENATED-STREAM}, которая
принимает любое количество потоков ввода в качестве аргументов.
Еще существуют два вида двунаправленных потоков, которые могут подключать потоки друг к
другу~--- \lstinline{TWO-WAY-STREAM} и \lstinline{ECHO-STREAM}. Их функции-конструкторы,
\lstinline{MAKE-TWO-WAY-STREAM} и \lstinline{MAKE-ECHO-STREAM}, обе принимают два аргумента, поток
ввода и поток вывода, и возвращают поток соответствующего типа, который можно использовать
как с потоками ввода, так и с потоками вывода.
В~случае \lstinline{TWO-WAY-STREAM} потока, каждое чтение вернёт данные из потока ввода, и
каждая запись пошлёт данные в поток вывода. \lstinline{ECHO-STREAM} по существу работает точно
так же кроме того, что все данные прочитанные из потока ввода также направляются в поток
вывода. То есть поток вывода потока \lstinline{ECHO-STREAM} будет содержать стенограмму
<<беседы>> двух потоков.
Используя эти пять типов потоков, можно построить почти любую топологию сети потоков,
какую бы вы ни пожелали.
В~заключение, хотя стандарт Common Lisp не оговаривает какой-либо сетевой API, большинство
реализаций поддерживают программирование сокетов и, как правило, реализуют сокеты как ещё
один тип потока. Следовательно, можно использовать с ними все обычные функции
вводы/вывода\pclfootnote{Наибольшим пробелом в стандартных средствах ввода/вывода Common
Lisp является отсутствие способа определения пользователем новых типов потоков. Однако,
для определения пользователем потоков существует два стандарта де-факто. Во время
разработки стандарта Common Lisp, Дэвид Грэй (David Gray) из Texas Instruments предложил
набросок API, который позволяет пользователям определять новые типы потоков. К
сожалению, уже не оставалось времени для разбора всех трудностей, поднятых этим
наброском, чтобы включить его в стандарт. Но все же много реализаций поддерживают в
некоторой форме так называемые потоки Грэя. Они основывают свой API на наброске
Грэя. Другой, более новый API~--- Simple Streams (простые потоки)~--- были разработаны
компанией Franz и включены в Allegro Common Lisp. Они были разработаны, чтобы улучшить
производительность определяемых пользователем потоков, схожих с потоками Грэя. Простые
потоки позже переняли некоторые открытые реализации Common Lisp.}.
Теперь вы готовы двигаться дальше для написания библиотеки, которая позволит сгладить
некоторые различия поведения основных функций для работы с файловыми путями в различных
реализациях Common Lisp.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End: