Точно Не проект 2 / Не books / Источник_1
.pdf250 |
Глава 5 |
|
|
Для проверки типов данных в Лиспе имеется большое число встро-
енных предикатов (ATOM, CONSP, LISTP, NUMBERP, STRINGP, ARRAYP и
др.). Определить принадлежность данных к одному из типов, указанных на рисунке 5.10, можно также с помощью обобщенного предиката проверки типа:
(typep объект тип-данного)
Например,
(typep ’(a b c) ’list) Т
Общий тип
Атомы |
Последовательности |
Функции Потоки Числа Литеры Символы Массивы
Рациональные |
Вещественные |
Векторы |
Списки |
Целые Дробные Одинарной Удвоенной Длинные |
Строки |
Точечные пары |
||
точности |
точности |
(long) |
|
|
(single) |
(double) |
|
|
|
Фиксированной |
Неограниченной |
разрядности |
разрядности |
(fixnum) |
(bignum) |
Рисунок 5.10 – Классификация основных типов данных языка Лисп
Выяснить тип объекта позволяет функция TYPE-OF, которая возвращает в качестве результата тип своего аргумента:
(type-of ’(a . b)) CONSP
Получить описание объекта можно с помощью функции DESCRIBE:
(setf x ’(a . b))
Язык Лисп |
251 |
|
|
(describe x) (a . b) is a cons
Для преобразования одного типа данных в другой используется обобщенная функция преобразования
(coerce объект тип)
Рассмотрим подробнее некоторые из типов данных, представленных на рисунке 5.10.
Символы (symbol). Ранее уже отмечалось, что с символом может быть связано значение, список свойств и определение функции. В общем случае символ представляет структурную переменную, состоящую из пяти элементов (рисунок 5.11):
-имени символа (отображается в виде строки);
-указателя значения символа;
-указателя свойств;
-указателя определения функции;
-указателя пакета (пространства имен, к которому принадлежит
символ).
Для доступа к каждому из указанных элементов используются сле-
дующие функции:
-к имени символа – (symbol-name символ);
-к значению символа – сам символ или (symbol-value символ);
-к определению функции – (symbol-function символ);
-к списку свойств – (symbol-plist символ) и (get символ имя_свойства);
-к пакету – (symbol-package символ).
Изменение составных элементов символа выполняется с помощью следующих средств:
-имя символа – (setf (symbol-name символ) значение);
-значение символа – (setq символ значение), (setf символ значение).
-определение функции – (setf (symbol-function символ) лямбда-
выражение) или (defun …);
-список свойств – (setf (symbol-plist символ) список-свойств), (setf (get символ имя-свойства) значениесвойства).
Если требуется включить в имя символа пробел или скобку или другой специальный знак, то применяют знак отмены “\” (backslash). Например,
(setf \ (а\ b\) 5) 5
Другой способ включения в имя символа указанных знаков состоит в написании имени между двумя вертикальными линиями:
252 |
Глава 5 |
|
|
(setf | (a b ) | 5) 5
Пакет
Список |
Имя |
Определение |
свойств |
символа |
функции |
|
|
|
Значение
Рисунок 5.11-Элементы, образующие символ
Принадлежность объекта данных к типу данных symbol может быть установлена предикатом:
(symbolp объект)
Литеры (character). Литеры представляют собой знаки языка и изображаются в виде #\a. Данная запись соответствует литере ‘a’. Значением литеры является сама литера. Чтобы выяснить, относится ли объект данных к литерному типу, применяется предикат
(characterp объект)
Литера может быть преобразована в строку с помощью функции STRING:
(string #\a) ”A”
Строки (string). Строка состоит из последовательности литер (знаков), заключенных в двойные кавычки. Например, “яблоко”. Строка, записанная таким образом, представляет собой константу, и ее значением является сама строка.
Для работы со строками в Лиспе имеется большой набор функций. Конкатенация (объединение) строк выполняется функцией CONCATENATE. Например,
(concatenate ’string ”abc” ”def”) ”ABCDEF”
Выделение из строки заданной литеры реализуется функцией CHAR:
(char ”abcde” 3) #\d (char ”abc d” 3) #\SPACE
Язык Лисп |
253 |
|
|
Здесь #\SPACE представляет неотображаемую литеру “пробел”. Имеются и другие, неотображаемые литеры, например: #\RETURN – перевод строки, #\TAB – табуляция.
Изменить произвольный элемент строки можно с помощью формы SETF:
(setf (char ”торт” 0) #\п) ”ПОРТ”
Строки можно сравнивать с помощью функций STRING=, STRING>, STRING<. Сравнение строк выполняется в лексикографическом порядке.
Возможны преобразования символа в строку
(string ’symbol) ”SYMBOL”,
а также двусторонние преобразования “строка – список литер ”:
(coerce ”SYMBOL” ’list) (#\S #\Y #\M #\B #\O #\L) (coerce ’(#\A #\T #\O #\M) ’string) ”ATOM”
Чтобы выяснить, относится ли объект к строковому типу, применяется предикат
(stringp объект)
Для создания символа из строки и внесения его в пространство имен применяется функция INTERN:
(intern строка)
Векторы (vector). Векторы в Коммон Лиспе представляют одномерные массивы. Элементами векторов могут быть числа, символы, списки или другие векторы. N – элементный вектор отображается в виде последовательности элементов, заключенной в круглые скобки, перед которыми стоит знак #:
#( x1 x2 … xn)
Создание векторов выполняется с помощью функций VECTOR или MAKEARRAY. Например,
(vector ’a ’b ’c ’d) # (A B C D)
(make-array ’(4)) # (NIL NIL NIL NIL) (make-array ’(3) :initial-element ’b) #(B B B)
(make-array ’(4) :initial-contents ’(a b c d)) #(A B C D)
Указанные функции обычно применяют в комбинации с формой SETF:
(setf v (vector ’a ’b ’c ’d)) #(A B C D) v #(A B C D)
(setf p (make-array ’(4) :initial-contents ’(1 2 3 4)) #(1 2 3 4) p #(1 2 3 4)
254 |
Глава 5 |
|
|
Ключевые параметры :initial-element и :initial-contents обеспечивают соответствующие начальные значения элементам вектора.
Доступ к элементам вектора выполняется в соответствии с позицией, которую занимает элемент в векторе. Номер позиции называют индексом. Нумерация начинается с нуля. Для доступа к элементам вектора применяется функция AREF:
(aref v 2) С
(aref p 0) 1
Изменение значений векторов выполняется с помощью присваивания:
(setf (aref v 2) 5.14) 5.14 v # (A B 5.14 D)
Векторы могут иметь указатели заполнения (fill-pointer). Указатель заполнения позволяет обозначить активные элементы вектора и соответствует наибольшему индексу элемента, который входит в активную часть вектора. Значение указателя заполнения определяется ключевым параметром FILL-POINTER функции MAKE-ARRAY:
(setf stack (make-array ’(9) :fill-pointer 0)) #( ).
Многие функции выполняют действия над векторами с учетом значения указателя заполнения, например, функции VECTOR-PUSH и VECTOR-POP, позволяющие организовать стековый механизм работы с вектором. При добавлении элемента в стек (функция VECTOR-PUSH) он вносится по индексу, соответствующему указателю заполнения, после этого значение указателя заполнения автоматически инкрементируется. В качестве результата функция VECTOR-PUSH возвращает индекс добавленного элемента, а функция VECTOR-POP – значение удаленного элемента. При удалении выполняются обратные операции. Выяснить текущее значение указателя можно с помощью функции FILL-POINTER:
(vector-push ’a stack) 0 (vector-push ’b stack) 1 stack #(A B)
(fill-pointer stack) 2
(vector-pop stack) В
(fill-pointer stack) 1 stack #(А)
Функция VECTOR-PUSH возвращает значение NIL, если указатель заполнения достиг предельного значения, соответствующего размерности вектора. Функция VECTOR-POP генерирует ошибку, если указатель заполнения равен нулю.
Отметим, что строка – частный случай вектора, элементами которого являются литеры. Поэтому к строкам могут применяться функции, определенные над векторами.
Выяснить принадлежность объекта данных к векторному типу можно с помощью предиката:
(vectorp объект)
Язык Лисп |
255 |
|
|
Последовательности (sequence). Последовательности представляют упорядоченные множества элементов данных. В Коммон Лиспе последовательности обобщают понятие векторов и списков. На элементы последовательности можно ссылаться по их позиции в последовательности. Над последовательностями можно выполнять общие действия, как-то:
-определение длины последовательности (LENGTH);
-объединение последовательностей (CONCATENATE);
-обращение последовательности (REVERSE);
-определение позиции элемента в последовательности (POSITION);
-удаление элемента из последовательности (REMOVE);
-замена элементов последовательности (SUBSTITUTE);
-заполнение последовательности (FILL) и др.
Создание последовательности можно выполнить с помощью функции MAKESEQUENCE, имеющей формат:
(make-sequence тип количество &KEY :initial-element)
Обязательные параметры функции MAKE-SEQUENCE определяют тип и количество элементов последовательности. Например,
(setf s (make-sequence ’list ;тип элемента последовательности
5 ;количество элементов
:initial-element ’a)); инициализация
( A A A A A)
Доступ к элементу последовательности выполняется вызовом функции ELT:
(elt последовательность индекс)
Например, (elt s 4) А.
Изменить значение элемента последовательности можно с помощью присваива-
ния:
( setf (elt s 3) ’B) В
S (А А А В А)
Массивы (array). Массивы в языке Лисп могут иметь произвольное количество измерений, в том числе и ноль. Массив нулевой размерности содержит один элемент. Любая версия Коммон Лиспа должна поддерживать, как минимум, работу с семимерными массивами. Создание массива выполняется функцией MAKE-ARRAY:
(setf feld (make-array ’(2 3) :initial-contents ’((a b c)(d e f))))
#2A ((A B C)(D E F))
Для доступа к элементам массива применяется функция AREF:
(aref feld 0 1) В
Изменение значений элементов массивов выполняется присваиванием по указателю, определяемому с помощью вызова функции AREF:
256 |
Глава 5 |
|
|
(setf (aref feld 1 2) ‘Z) Z feld #2A((A B C)(D E Z))
Определить размерность массива можно с помощью функций:
(array-dimension feld 1) 3
(array dimensions feld) (2 3)
Выяснить, принадлежит ли объект данных к типу массив, можно с помощью предиката:
(arrayp объект)
Потоки (stream). В Лиспе операции ввода-вывода выполняются с помощью потоков. Потоки представляют собой хранилища данных, из которых можно читать данные или в которые можно записывать данные. Потоки могут быть связаны с устройствами ввода-вывода, файлами на дисках, строками в памяти. Поток характеризуется направлением (direction) и типом элементов, которые берутся из потока или направляются в поток.
Поток в Лиспе связывается с динамической переменной, значения которой и представляют поток. Имеется несколько стандартных потоков: *STANDARD-INPUT*, *STANDARD-OUTPUT*, *ERROR-OUTPUT*, *TRACE-OUTPUT* и др. Эти динами-
ческие переменные определяют для функций ввода (READ) и вывода (PRINT) файлы ввода-вывода. В начале сеанса работы с Лисп системой эти файлы связаны с терминалом.
Если требуется выполнять ввод-вывод из других файлов, то их необходимо открыть. Для этого применяется функция OPEN, упрощенный формат которой можно представить в форме
(open имя-файла &KEY
|
: direction значение |
|
: element-type значение |
|
: if-exists значение |
|
: if-does-not-exist значение) |
Ключевые параметры имеют следующие значения: |
|
direction |
– направление передачи данных в потоке; возможные значения: 1) |
|
:input – ввод; 2) :output – вывод; 3) :io – двунаправленный поток и |
element-type |
др.; |
– тип элементов, передаваемых в потоке (по умолчанию |
|
if-exists |
character); |
– выполняемые операции, если заданный файл уже существует; |
|
|
возможные значения: 1) :error - ошибка; 2) :override – перезапи- |
|
сать; 3) :append – добавить новые записи в конец; 4) :rename – |
if-does-not-exist |
переименовать и др.; |
– выполнение операций, если заданный файл отсутствует; возмож- |
|
|
ные значения: 1) :error – ошибка (для файлов открытых в режиме |
|
чтения); 2) :create – создать. |
Пример вызова функции OPEN:
Язык Лисп |
257 |
|
|
(setf vyvod (open ”extern.dat” :direction :output :if-exists :append
:if-does-not-exist :create))
Здесь функция OPEN направляет выходной поток в файл ”extern.dat”, а функция SETF связывает с открытым потоком символ VYVOD, который в дальнейшем используется в качестве имени потока.
Связи стандартных потоков могут быть переопределены с помощью присваивания. Например,
(setf *standard-output* vyvod)
После завершения работы с потоком его необходимо закрыть
(close vyvod)
Выяснить, является ли объект данных потоком, можно с помощью предиката
(streamp объект)
5.16. Ввод-вывод
При работе с интерпретатором Лиспа ввод-вывод выполняется автоматически. Интерпретатор читает вводимое пользователем s-выражение (READ), вычисляет его (EVAL) и выводит результаты (PRINT). В простейшем случае функция READ не требует аргументов. После вызова этой функции происходит обращение к стандартному входному потоку и считывание введенного пользователем s-выражения. Введенное выражение возвращается в точку вызова READ.
Определим функцию, которая находит сумму четырех введенных с клавиатуры чисел:
(defun sum4 ( )
(+ (read) (read) (read) (read))
Для вызова этой функции необходимо напечатать (sum4), а затем ввести четыре числа:
> (sum4)
1 2 3 4
10
>
Если требуется вводить данные из файлов, то сначала необходимо открыть c помощью функции OPEN соответствующий входной поток:
(setf vvod (open ”extern.dat” :direction :input))
258 |
Глава 5 |
|
|
Для чтения выражений из открытого потока VVOD необходимо сообщить его имя функции READ, т.е.
(read vvod)
В этом случае произойдет обращение к файлу ”extern.dat”, и из него будет считано одно s-выражение. Результатом вызова функции READ будет s-выражение, введенное из файла.
Для вывода значений в Лиспе используется функция PRINT. Эта функция имеет один аргумент, значение которого выводится в выходной поток. Функция PRINT перед выводом значения осуществляет переход на новую строку, a после печати значения выводит пробел
(print ’(a b c)) (a b c)
(А В С)
Здесь список (А В С) дважды отображается на экране. Дополнительный автоматический вызов функции PRINT происходит в цикле READ-EVAL-PRINT интерпретатора.
Для вывода значений могут также использоваться функции PRIN1 и PRINC. Функция PRIN1 аналогична PRINT, но не выполняет переход на новую строку и не выводит пробел. Функция PRINC дает возможность напечатать строку без ограничивающих кавычек:
(princ ”lisp”) lisp
Для перехода при выводе значений на новую строку можно использовать функцию TERPRI. У функции TERPRI нет аргументов. Если требуется вывести на печать имя символа, содержащее пробелы, скобки или строчные и заглавные буквы, то применяют знак отмены “\” или ограничивающие вертикальные линии “|”. Все рассмотренные функции вывода позволяют осуществлять вывод в выходной поток открытый, с помощью функции OPEN:
(setf stran (open ”extern.dat” |
:direction :output :if-exists :append)) |
(print ’|a b c d| stran) |a b c d| |
|
(print ’\a\C \ \d stran) |aC |
d| |
(princ ’|a b c d| stran) |a b c d|
Здесь вторым аргументом функции является выходной поток STRAN, связанный с файлом EXTERN.DAT.
При работе с файлами удобно использовать макроформу WITH-OPEN- FILE. Она позволяет открыть поток, задать режим работы с ним, выполнить необходимую его обработку и закрыть его:
Язык Лисп |
259 |
|
|
(with-open-file (поток имя-файла {опция}*) {декларация}* {форма}*)
Вызов WITH-OPEN-FILE базируется на использовании функции OPEN. Режимы открытия потока задаются с помощью аргумента опция. Они полностью соответствуют ключевым параметрам, указываемым при вызове функции OPEN. Формы вызова WITH-OPEN-FILE вычисляются последовательно. В качестве результата возвращается значение последней формы. В приведенном ниже примере открывается поток с именем STORE, который связывается с файлом TEST1.LSP. В файл циклически записываются s- выражения, вводимые с клавиатуры. Формирование файла завершается, если с клавиатуры вводится атом EOF:
(defun writer ( ) |
|
|
(with-open-file (store ”test1.lsp” |
: direction |
:output |
|
: if-exists |
: append |
: if-does-not-exist :create ) (prinс ”Введите s-выражение”)
(terpri)
(princ ”Для завершения ввода напечатайте EOF”)
(terpri)
(do ((item (read) (read))) ;чтение s-выражения ((eq item ’eof ) ’end-of-session )
(print item store ) )))
Для управления формой вывода в Коммон Лиспе применяется функ-
ция FORMAT:
(format поток шаблон &REST аргументы )
Если поток задан символом Т, то функция осуществляет вывод на экран. Шаблон представляет собой строку, которая может содержать коды для управления печатью. Перед управляющими кодами записывается знак “~” (тильда). Если в шаблоне нет управляющих кодов, то функция FORMAT выводит строку шаблона так же, как и функция PRINC. Некоторые управляющие коды и их назначения приведены в таблице 5.2.
Если в строке шаблона встречается символ перехода на новую строку, то при печати будет выполняться переход на новую строку. Приведем пример:
(format t ”Смотри: ~S! ~% А сейчас по-английски: ~R dog ~:P ” ’| A B | 5 )
Смотри: | A B | !
Асейчас по-английски: five dogs