[an error occurred while processing this directive]
.цв
6. REPRISE.
.ов
В ранее рассмотренных примерах программ на LISP используемые слова представлялись как в виде символьных данных (обычно перед ними следует символ '), так и в виде переменных которые хранят некоторые значения. Аналогично, списки могут также быть представлены в виде символьных данных и в виде представленных в языке LISP фрагментов программ. Эти программы могут строится из функций, образующих списки и осуществляют связь между именами и величинами.
Слова в LISP могут быть идентификаторами и "атомами" и являются наиболее элементарными объектами при создании структур данных и программ на LISP. Различие между ними заключается в том, что идентификаторы состоят из строк буквенных символов (например, A, NIL, GOLDEN-WONDER), тогда как атомы могут включать и числа (например, 2, 9999, -77). Все списки, котрые мы рассмотрели, как данные, так и программы, записаны в следующем виде: левая скобка, затем последовательность атомов и подсписков и затем правая скобка. В следующей главе мы рассмотрим, каким образом такие структуры представляются в LISP и в процессе рассмотрения увидим, что эти списки являются специальным случаем так называемых "s-выражений", которые строятся из элементарных структур, называемых "точечными парами".
Т.о., мы рассмотрели:
(a) Как хранить информацию как характеристику идентификатора, используя PUT:
(PUT идентификатор характеристика-имя характеристика-значение)
(b) Как управлять этой характеристикой после установления:
(GET идентификатор характеристика-имя)
(c) Как присвоить значения идентификаторам:
(SETQ идентификатор значение)
(d) Как обратиться к значению идентификатора и как различать использование переменных и символьных данных.
'literaldata
variablename
(e) Как определить простые функции, используя DEFUN и как строить выражения, используя все вышеприведенные средства для построения, хранения и поиска данных и сообщений.
(DEFUN имя функции (аргумент1 аргумент2...аргументn)
тело функции)
Очевидно, нам понадобится также знать как изменять характеристики, нарушать структуры или делать копии их фрагментов и создавать тесты и сравнения данных LISP.
.цв
7. СТРУКТУРЫ ДАННЫХ.
.ов
Часто бывает полезно иметь диаграммное представление структур, на которых работает ваша программа. В этой главе представлен один из таких способов представления для LISP и имеет целью проиллюстрировать то, каким образом LISP обрабатывает получаемые данные.
Рассмотрим список: он состоит из некоторого числа элементов, после каждого из которых, кроме последнего, идет следующий элемент. LISP обрабатывает списки как класс оъектов, к которым можно обращаться в универсальной форме, независимо от их длины. Весьма полезна идея просмотра списка по одному элементу за один проход.
LISP также позволяет свободно перемешивать идентификаторы и числа со списками. Конца любого списка можно достичь с помощью указателей. (см. рис.)
В случае списков такой указатель определяет ячейку, каждая из двух частей которой также содержит свой указатель. (см.рис)
????????????????????????????????????????????????????????????????
Левый указатель ведет к первому элементу хранящегося списка, а правый указатель определяет оставшийся. Т.о., для списка (A B C 1 2...) мы получаем: (см.рис.)
Сейчас нет необходимости детально рассматривать, каким образом LISP представляет слова и числа (см. главу 21). Разумеется, элементы не обязательно должны быть атомами. Например, мы можем иметь следующий список:
(A (список...) значения...)
см. рис.
????????????????????????????????????????????????????????????????
В самом деле, списки и подсписки могут размещаться на произвольных уровнях: (см.рис.)
????????????????????????????????????????????????????????????????
LISP имеет две основные функции для следования цепи указателей в структуре списка. CAR определяет левый указатель, а CDR - правый. Для рассмотренных здесь списков CAR выбирает первый элемент списка, а CDR - оставшийся.
см.рис.
????????????????????????????????????????????????????????????????
Функции CAR и CDR используются в LISP как основные функции выбора. Эти функции - акронимы, относящиеся к полному названию функции адресных структур. Следуя тенденции рассмотрения их как левого и правого подполя одной ячейки, они стали рассматриваться как просто технические термины для определения функций выбора.
Из показанных выше диаграмм списковых структур можно видеть, что, если указатель x обращается к списку, то функция (CAR x) обращается к первому элементу списка. Аналогично, (CDR x) относится к подсписку без первого элемента. Комбинации этих двух функций могут выбирать любой нужный компонент списка: например:
(SETQ A '(A (СПИСОК) (ПОДСПИСОК)))
после того, как данная строка установила A для обращения к списку длиной три, элементы могут быть выбраны следующим образом:
(CAR A) = идентификатор A
(CDR A) = список из двух элементов, ((СПИСОК) (ПОДСПИСОК))
(CAR (CDR A)) = список с одним элементом (СПИСОК)
(CAR (CAR (CDR A))) = идентификатор СПИСОК
(CDR (CDR A)) = список с одним элементом ((ПОДСПИСОК))
CAR (CDR (CDR A))) = список с двумя элементами (ПОДСПИСОК)
и так далее. Изображая структуры с помощью указателей, как показано в примере, вым станет понятно использование функций CAR и CDR.
В предыдущем примере A - это список из трех элементов; (CDR A) имеет длину два, а (CDR (CDR A)) - длину 1. Из этого следует, что (CDR (CDR (CDR A))) означает список без элементов, т.е. пустой список, который может быть записан как (). LISP имеет специальное обозначение для пустого списка - идентификатор NIL и при чтении выражений LISP обрабатывает NIL и () как эквивалентные записи. Поэтому, полная диаграмма для простого списка типа (A B) имеет следующий вид: см.рис.
Т.к. структуры, имеющие в своем составе пустой список, используются очень часто, то для удобства обычно заключительное CDR поле заполняется не указателем к NIL, а штрихом:
(((ODD)) (STRUCTURE))
см. рис.
????????????????????????????????????????????????????????????????
В данной книге под словом "список" понимаются структуры, объединенные вместе как показано в примерах выше, а NIL четко определяет для функции CDR направление. LISP позволяет создавать и более общие структуры. Например: см рис.
????????????????????????????????????????????????????????????????
Мы предоставляем право читателю выбрать форму представления данных структур (может быть, вид структур, выданный принтером, будет более предпочтителен). Термины "списковая структура" или "s-выражение" будут использоваться в таких случаях. Функции CAR и CDR, разумеется, не связаны со свойствами структур, с которыми они работают. Поэтому, они могут использоваться с любыми последовательностями указателей при разработке программ на LISP.
Другие функции не являются столь понятными, и, поэтому, следующая глава содержит объяснение способов построения как списковых структур, так и s-выражений. Также в ней содержится описание того, как печатаются общие структуры и каким образом можно избежать обращения к функциям CAR и CDR в ряде случаев.
.цв
ДРУГИЕ СТРУКТУРЫ ДАННЫХ
.ов
На языке диаграмм, использовавшихся в главе 7, элементарная функция построения списков CONS может быть представлена как заполнение новой ячейки и внесение в нее указателей функций CAR и CDR. Таким образом, (CONS 'A 'B) дает следующую структуру: см. рис.
Аналогично, (CONS 'ITEM NIL) - это представление списка (ITEM). Функция CONS также может быть, очевидно, использована для внесения новых элементов в начало существующего списка. Если переменная L относится к списку, тогда (CONS p L) представляет список содержащий на один элемент больше, где p является первым элементом, а L - старый список, который в новом списке следует за p. Из диаграмм видно, что для любых u и v
(CAR (CONS u v)) = u и (CDR (CONS u v)) = v
При помощи внутренних обращений к функции CONS можно построить любой список, требующийся пользователю LISP, но для создания больших списков использование этой функции неудобно. В таких случаях используется функция LIST, которая является более короткой записью нескольких внутренних обращений к функции CONS. Например, (LIST 'DATA 'FOR NAME') может быть записано вместо:
(CONS 'DATA (CONS 'FOR (CONS NAME NIL)))
Число функций CONS, которое объединяет функция LISP равно числу аргументов. Например, если функция LISP не имеет аргументов, она возвращает пустой список, NIL, без выполнения функций CОNS. Можно видеть, что функция LIST строит результирующий список с конца, используя каждую внутреннюю функцию CONS для помещения нового элемента в начало создаваемого списка.
Также как и при обычном способе ввода списков, LISP обеспечивает формат, отражающий способ образования данных стуктур при помощи ячеек функции CONS. Этот способ в основном используется при нулевой структуре данных, так что структура не является собственно списком. При такой системе, результат функции (CONS 'A 'B) может быть записан как (A . B). Эта запись называется точечной парой. Запись '.' указывает на наличие ячейки, которая может иметь выражение вида CDR, являющееся атомом. Способ записи с помощью точечных пар может быть использован для списков, элемент, следующий за '.' является списком или NIL. При использовании данного способа список, который обычно вводится как '(1 2 3) будет представлен в виде :
'(1 .(2 3)), '(1 . (2 . (3)
где каждая пара скобок соответствует одной ячейке структуры.
Кроме того, списки могут быть записаны точечными парами, имея несколько элементов перед точками, что позволяет записать список '(1 2 3) еще двумя эквивалентными способами:
'(1 2 . (3)), '(1 2 3 . NIL).
В последнем из примеров показывается формат, который обычно выбирается для структур данных, не оканчивающихся NIL. Стандартная функция печати LIS, показывает, например, следующую структуру: см. рис.)
????????????????????????????????????????????????????????????????
(A FULL . STOP).
Все это многообразие форматов ввода может привести к затруднениям. Здесь предполагается, что выход из данного затруднения заключается в том, что вводимые строки должны быть представлены в виде эквивалентных диаграмм, при этом должны соблюдаться два правила, а именно : списки типа (A B...Z) представляются в виде: см.рис.
????????????????????????????????????????????????????????????????
и запись вида (ALFA...PSI . OMEGA) записывается в виде: см.рис.
????????????????????????????????????????????????????????????????
Здесь каждая буква в примерах выше может стоять за атомом или любым другим списком.
В большинстве простых применений LISP никогда не бывает необходимости использовать точечное представление для ввода, и, поэтому, все создаваемые структуры данных будут представлять собой простые списки и точки не появляются как выходные данные при работе с LISP.
Функция CONS позволяет очень просто присоединить новые объекты к началу списка. Представление в виде диаграмм позволяет видеть, что присоединение каких-либо элементов к концу существующего списка является более сложной операцией. В сущности, эта операция включает в себя создание копии расширяемого списка с заменой последнего нулевого уазателя указателем, требующимся для расширения списка. Существует специальная функция LISP, которая делает это - APPEND (смотрите приложение B). Однако, большинство программ LISP работают удобнее и быстрее при использовании для создания списков функции CONS, добавляющей элементы к началу списка, нежели при использовании APPEND, добавляющей элементы в конец списка. В сущности, при необходимости добавления элементов в конец списка полезно уметь изменять указатели в существующих списках. Это позволяет иногда существенно сократить время, необходимое для копирования структур в процессе преобразования их из изначального в требуемый формат. Функции LISP, обеспечивающие эти операции, называются RPLACA и RPLACD. Эти названия образованы от слов "replace" (перемещение) и "A" или "D", означающие изменяемое поле CAR или CDR. Если L относится к определенной CONS ячейке, то (RPLACA L 'ASTON-MARTIN) переписывает CRNS ячейку так, что L будет относиться к атому ASTON-MARTIN. RPLACD работает аналогично, переписывая ячейку CDR. Обе функции возвращают обращение к ячейке, определяемой первым аргументом функций, например:
(RPLACD (RPLACA L новое CAR) новое CDR)
меняет оба поля L. Корректировка существующих списков может иметь неприятные последствия. При переписывании ячейки две структуры, хранящиеся в ней, подвергаются изменению, поэтому, при использовании RPLACA/D очень важно сделать копии структур, которые необходимо сохранить и которые являются при этом объектами, подлежащими переписыванию. Также, конструкции типа:
(RPLACA L L) и (RPLACD L L)
могут строить списки, являющиеся циклическими и вследствие этого требующими нескольких встроенных функций LISP (включая функции печати). В современных системах LISP существует тенденция отказа от использования RPLAC функций, имея в виду то обстоятельство, что при всех случаях экономии времени они не избавляют от проблем, которые вызывает их использование.
Существует тенденция построения программ LISP вокруг структур данных, состоящих из нескольких CONS ячеек, где группа из трех-четырех ячеек рассматривается как отдельный блок. Если эти ячейки организованы как линейная цепь в направлении CDR, с нулевым элементом в конце, функция LIST может построить структуру данных достаточно легко. В других случаях полезно использовать программы, введенные пользователем, которые произведут все необходимые CONS сразу. Аналогично, полезно иметь функции, соответствующие сочетаниями CAR и CDR, которые выбирают элементы из маленьких списков структуры. В LISP существует комплект таких функций доступа, соответствующих всем возможным комбинациям из трех функций CAR и CDR. Имена этих функций образуются стандартным методом: каждое имя начинается с буквы "C" и заканчивается буквой "R" и содержат символы "A" между ними для функций CAR и "D" для каждой CDR. Следующие примеры иллюстрируют вышесказанное:
(CAAR x) = (CAR (CAR x)) (CADR x) = (CAR (CDR x)) (CDAR x) = (CDR (CAR x)) (CDDR x) = (CDR (CDR x)) (CADDR x) = (CAR (CDR (CDR x))) (CDAAR x) = (CDR (CAR (CAR x)))
см.рис.
????????????????????????????????????????????????????????????????
Полные имена этих функций могут быть труднопроизносимыми, и если в какой-либо программе имеется необходимость в их использовании, то пользователю следует выбрать более удобное имя для осуществления операций обращения.
Нам остается рассмотреть в данной главе функцию LISP EQ, являющуюся простейшим тестом равенства. Работа данной функции заключается в том, что она осуществляет сравнение двух указателей и выясняет, относятся ли они к одному и тому же объекту. Она организована таким образом, что идентификаторы LISP хранятся в специальной таблице и все обращения к идентификаторам имеют одинаковый характер. Т.о., функция EQ обеспечивает способ непосредственной проверки, относятся ли указатели к одному и тому же идентификатору. Она оргаизована таким образом, что возвращает значение "истина" в представлении LISP, если ее оба аргумента - одинаковые числа. Аналогично, функция EQ возвращает значение "ложь", если один аргумент - идентификатор, а второй - число или список. Что касается списков, функция EQ содержит тест для проверки, определяют ли два указателя одну и ту же ячейку CONS или нет. Заметим, что т.к. при обращении к CONS всегда образуется новаю ячейка, т.е. образуется ячейка, отличная от всех существующих CONS, то всегда существует случай:
(EQ L (CONS (CAR L) (CDR L)) = ложь, даже если две сравниваемые списковые структуры имеют одинаковую форму и печатаются как одинаковые последовательности символов. Позже в данной книге мы рассмотрим функцию EQUAL, осуществляющую полный просмотр аргументов, анализируя их эквивалентность формы и содержимого. EQUAL, очвидно, работает медленнее чем EQ, но иногда возникает необходимость в ее использовании.
.цв
9. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ НА LISP.
.ов
В LISP числа являются особым видом атомных (т.е. не списковых) объектов. Числовые данные могут записываться при необходимости в любом месте фрагмента LISP - знак "'", требующийся для записи всех других видов данных, не является необходимым. Т.о., можно записать (SETQ COUNT 0), что является эквивалентным записи (SETQ COUN '0'). Поскольку числовые значения интерпретируются таким образом, то, разумеется, нельзя использовать числа как имена переменных. Такая запись, как (SETQ 1 2) является недопустимой и приведет к выдаче сообщения об ошибке. LISP допускает арифметические операции с 16-разрядными целыми числами, т.е. можно оперировать с числами в пределах от 2^15 до (2^15)-1 или от -32768 до 32767. Любая попытка ввести число, выходящее за эти пределы приведет к сообщению об ошибке. Факт обнаружения переполнения и сообщение системы о нем является компенсацией недостатка использования узкого диапазона числовых данных.
Синтаксис LISP не содержит специальных символов для осуществления арифметичских операций, таких как "+" и "-". Арифметические операции на LISP осуществляются таким же способом, как все другие средства языка LISP, т.е. посредством вызова соответствующих функций. Функция, складыающая два числа, называется PLUS, и для сложения необходимо записать:
(PLUS 2 2) (PLUS 1 2 3 4 5)
Как можно видеть во втором примере, функция PLUS может иметь много аргументов, и ее результатом будет являться сумма всех аргументов. (Заметим, что данная версия LISP позволяет иметь функции PLUS Максимум 28 аргументов). Арифметические операции часто путают с операторами присваивания и обращениями к переменным:
(SETQ N 3) (SETQ N (PLUS N N)) (SETQ N (PLUS N N 7))
Данная последовательность сначала устанавливает N равное 3, затем 6 и наконец, 19. Если функция PLUS осуществляет сложение, то функция TIMES - умножение, например:
(TIMES 10 100) = 1000
При обычном способе записи знак "-" используется в двух случаях: для обозначения отрицательной величины и для обозначения операции вычитания. Сравните: -(3+7) и (2+3)-(1+4). В LISP эти операции обеспечиваются двумя различными функциями: MINUS приписывает величине отрицательное значение, а DIFFERENCE означает операцию вычитания:
(MINUS (PLUS 3 7)) (DIFERENCE (PLUS 2 3) (PLUS 1 4))
Очевидно, что любые произвольно составленные арифметические выражения можно записать, используя вложенные функции, о которых говорилось выше. При обычной математической записи необходимо помнить, что, например, 3*4+2 означает (3*¤)+2, а не 3*(4+2), т.е. существуют приоритетные арифметические операции. В LISP благодаря скобковой структуре записи никогда не возникает трудностей, связанных с порядком выполнения арифметичских действий.
Деление осуществляется с помощью функций QUOTIENT и REMAINDER. Отсюда ясно, что остатки всегда положительны. Для положительных делимого и делителя это означает округление частного в меньшую сторону.
Для того, чтобы расчетная программа была более простой, LISP обеспечен функциями ADDl И SUBl, которые осуществляют соответственно уменьшение и увеличение своих аргументов. Их действие полностью эквивалентно соответствующим обращениям к функциям PLUS и DIFFERENCE, но они более коротки в написании и несколько быстре работают.
В LISP любая переменная может содержать список, идентификатор или число. Иногда полезно уметь указать, к какому из этих классов относится величена.
Функция ATOM различает, является ли объект списком (не являющимся атомом) или нет. Функция NUMBER распознает числа из атомов. Проверку чисел осуществляют функции EQ, GREATERP, LESSP, MINUSP, ONEP и ZEROP, действие которых показано в приложении 1.
В Простом примере арифметического выражения показана корректировка записи базы данных, содержащей возраст какого-то лица:
(DEFUN BIRTHDAY (PERSON) (PUT PERSON 'AGE (ADDl (GET PERSON 'AGE))) (LIST 'HAPPY 'BIRTHDAY PERSON))
В данной версии LISP не предпринимались особые усилия для особенного удобства арифметических вычислений. Это связано с тем, что большинство программ LISP работают в большей степени с текстовой информацией, нежели с числовой. Однако полезно отметить, что современные многоцелевые версии LISP почти также хорошо компилируют числовые коды, как и другие современные языки.
Один из примеров программ, данных в главе 23, показывает, каким образом произвольной точности арифметические действия могут быть построены на основе, описанной в данной главе. Одним из достоинств LISP является то, что если необходимо использовать расширенные арифметические действия, можно переопределить функции PLUS, TIMES, NUMBER и другие арифметические функции и использовать полные арифметические функции, а не встроенные. Эффект будет такой же, как если бы основная версия LISP обеспечивала выполнение арифметических операций с произвольной точностью.
.цв
10. ТЕСТЫ И СРАВНЕНИЯ.
.ов
Средства, позволяющие определить функцию посредством простоговызова, построение структурной информации, подлежащей хранению или дальнейшей обработке или произведение над ней арифметических операций, были уже рассмотрены. Однако, гораздо более интересные примеры программ могут быть получены при написании функций, использующих некоторый "интеллект" - анализирующих свои данные аргументы и принимающих решение, что с ними делать дальше в зависимости от результата анализа. Другими словами, следующим нашим шагом будет рассмотрение средств LISP, позволяющих осуществлять сравнение и анализ данных.
Фактически, некоторые функции тестирования мы уже упоминали: EQ, ATOM, NULL. Эти функции возвращают значения, представляемые булевой алгебры "истина" и "ложь". В LISP NIL используется для установления значения "ложь" (а также для пустого списка), а все данные, не являющиеся NIL рассматриваются как "истина". Обычно используется идентификатор T и эта пременная изначально имеет ненулевое значение ('Т).
Аналогично, переменная F изначально имеет значение NIL и т.о., T и F производят оценку является ли параметр "истинным" или "ложным".
Функции LISP, возвращающие истинные величины, называются утверждениями. Они используются вместе с условными выражениями при образовании специальной функции COND. Обычная форма таких выражений имеет вид:
(COND (утверждение1 выражение1) (утверждение2 выражение2) ... (T последнее выражение))
Можно образовать сколько угодно таких пар утверждение (предикат)-выражение. Когда LISP обнаружит COND выражение, он начнет производить оценку утверждений в данном порядке. Эта функция возвращает значение, являющееся результатом выражения, стоящего в паре с первым ненулевым (т.е. истинным утверждением).
В примере выше константа Т используется как последнее (заключительное) утверждение. Т.к. оно всегда имеет значение "истина", то соответствующее заключительное выражение может рассматриваться по умолчанию как результат, который возвращается функцией COND при условии, что все другие утверждения ложны. Т.о., записанная без скобок условная форма выглядит следующим образом:
IF (если) утверждение1 - истина THEN (тогда) возвращается значение выражения1 ELSE IF (иначе если) утверждение - истина тогда возвращается значение выражения2 ...
Иначе говоря, если ни одно из утверждений не было ненулевым, возвращается значение заключительного выражения.
Простым примером условной операции может служить определение функции вычисления абсолютного значения. Условная функция здесь анализирует знак аргумента. Отрицательные значения должны менять знак, а положительные возвращаются без изменения.
DEFUN ABS (NUMBER) (COND ((MINUSP NUMBER) (MINUS NUMBER)) (T NUMBER)
Здесь утверждение1 - (MINUSP NUMBER), выражение1 - (MINUS NUMBER), а окончательный результат - NUMBER. Скобковая структура может быть более наглядной, если основу функции писать одним цветом, утверждения - другим, а результат - третим!
В предыдущей главе приведены несколько примеров, основанных на использовании PUT и GET для помещения параметров в определенные списки. Если GET используется с целью обработки характеристики, которая не была установлена, то возвращается NIL, и т.к. NIL является представлением в LISP ложного утверждения, то легко можно составить программу тестирования вида:
... (COND ((GET NAME 'ADDRESS) (LIST 'ADDRESS 'IS (GET NAME 'ADDRESS))) (T '(ADDRESS UNKNOWN))) ...
Иногда является более удобным так организовать формат фрагмента программы, что вначале обрабатывается случаи с отстсутвующими характеристиками. LISP обеспечивает функцию NOT, которая может использоваться для присвоения утверждению обратного значения. Для любых выражений p, q и r следующие две записи эквивалентны:
(COND (p q) (COND ((NOT p) r) (T r)) (T q))
NOT может, разумеется использоваться в любой части программы LISP, не обязательно внутри условных структур. Из правил, приведенных выше для представления в LISP булевых величин можно заключить, что (NOT NIL) означает истинное значение, а (NOT любое не NIL) - ложное. Т.о., наряду с использованием NOT в качестве функции логического отрицания, ее также можно использовать как функцию распознавания NIL, т.е. пустых списков. LISP также имеет функцию, проверяющую, является ли аргумент пустым списком или нет: очевидно, что NOT И NULL ведут себя одинаково.
Если требуется образовать составной предикат, можно использовать AND и OR. Каждая из этих функций может иметь произвольное число аргументов. Они обрабатывают эти аргументы по одному до тех пор, пока обычные правила логики позволяют достичь результата. Т.о., AND обрабатывает аргументы до тех пор, пока не будет достигнут конец списка (и происходит возвращение значения Т) или до тех пор, пока не будет найдено значение NIL (т.е. ложное значение). OR останавливает обработку аргументов и возвращает Т как только будет обнаружен аргумент не NIL. Фрагменты программы LISP, требующие составных вложенных функций COND, AND, OR и NOT являются наиболе удобной формой записи из простфх функций.
Дополнительные предикаты LISP даны в разных частях данной книги и собраны вместе в приложении А. Они могут быть такими, как ATOM, проверяющим является ли выражение атомом (а не списковой структурой) и обрабатываться простейшим тестом на равенство EQ и арифметическими тестами MINUSP и GREATERP, а также они могут быть единицами типа EOF, проверяющими, достиг ли вводимый файл своего конца.
Полная форма COND позволяет записать последовательность для каждого предиката:
(COND (предикат1 rla rlb rlc...rlz) (предикат2...) ...
При использовании такой формы, если предикат1 не является NIL, каждое из rla...rlz обрабатывается поочередно и значение, полученное при обработке последнего из них, будет являться результатом, возвращаемым функцией COND. Очень полезным является наличие в некоторых компонентах последовательности функций печати и использование PUT или SETQ. Почти во всех примерах последующих глав используется этот расширенный формат.
.сс
[an error occurred while processing this directive]