Функция macro-function определяет, является ли данный символ именем макроса. Конструкция defmacro предоставляет удобный способ определить новый макрос.
Первый аргумент должен быть символом. Если символ содержит определение функции, то есть определение макроса, локально созданное в окружении env с помощью macrolet или глобально созданное с помощью defmacro, тогда возвращается функция раскрытия (функция двух аргументов, первый форма макровызова и второй — окружение). Если символ не содержит определение функции, или это определение обычной функции или это оператор, но не макрос, тогда возвращается nil. Наилучший способ вызвать функцию раскрытия использовать macroexpand или macroexpand-1.
Возможно такое, что и macro-function, и special-operator-p обе возвращают истину для одного заданного символа. Так происходит, потому что реализация может для увеличения производительности реализовывать любой макрос, как специальную форму. С другой стороны, определения макросов должны быть доступны для использования программами, которые понимают только стандартные операторы, перечисленные в таблице 5.1.
setf может быть использована с macro-function для установки в символ глобального определения макроса:
Устанавливаемое значение должно быть функцией, которая принимает два аргументы, список макровызова и окружение, и вычислять раскрытие этого макровызова. Выполнение этой операции указывает на то, что символ будет содержать только это определение макроса в качестве определения глобальной функции. Предыдущее определение функции или макроса утрачивается. С помощью setf невозможно установить локальное определение макроса. При использовании setf указание второго параметра (окружение) является ошибкой. Переопределение оператора также является ошибкой.
Смотрите также compiler-macro-function.
defmacro является макросом определяющим макросы, которые преобразуют формы макровызовов. defmacro имеет почти такой же синтаксис, как и defun: name является символом, для которого создаётся макрос, lambda-list похож на список параметров лямбда-выражения и form содержит тело функции раскрытия. Конструкция defmacro устанавливает функцию раскрытия, как глобальное определение макроса для символа name. FIXME
Форма обычно используется на верхнем уровне, но её также можно использовать на неверхнем уровне. Таким образом, defmacro должна определять функцию раскрытия макроса в некотором лексическом, а не глобальном окружении.
Тело функции раскрытия, определяемое с помощью defmacro, неявно оборачивается в конструкцию block, имя которой совпадает с именем name, определяемого макроса. Таким образом для выхода из функции может использоваться return-from.
Форма defmacro возвращает в качестве значение name.
Если мы рассмотрим макровызов, как список содержащий имя функции и некоторые формы аргументов, то механизм заключается в передаче в функцию apply этой функции и списка её (невычисленных) аргументов. Спецификаторы параметров обрабатываются также, как и для любого лямбда-выражения. В качестве параметров используются формы аргументов макровызова. Затем вычисляются формы тела, как неявный progn. Значение последней формы возвращается, как раскрытие макровызова.
Если указана необязательная строка документации doc-string, тогда она присоединяется к символу name, как строка документации типа function. Смотрите documentation. Если в определении макроса представлена только строка документации, и после неё нет ни одной формы, ни деклараций, ни просто для тела, то данная строка сама становиться формой, и тело считается состоящим из одной формы — этой строки.
Следующие три дополнительных маркера доступны в определении лямбда-списка.
Смотрите lambda-list-keywords.
X3J13 voted in March 1989 to specify that macro environment objects received with the &environment argument of a macro function have only dynamic extent. The consequences are undefined if such objects are referred to outside the dynamic extent of that particular invocation of the macro function. This allows implementations to use somewhat more efficient techniques for representing environment objects.
X3J13 voted in March 1989 to clarify the permitted uses of &body, &whole, and &environment:
defmacro, в отличие от любых других конструкций Common Lisp’а, имеющих лямбда-списки в своём синтаксисе, предоставляет дополнительную функциональность известную как деструктуризация.
Смотрите destructuring-bind, которая отдельно предоставляет эту функциональность.
В любом месте, где могло бы стоять имя параметра, и где по синтаксису не ожидается использование списка (как описано в разделе 5.2.2), можно использовать ещё один встроенный лямбда-список. Когда использован такой приём, при передаче форм аргументов для встроенного лямбда-списка, необходимо обернуть эти формы в отдельный список. В качестве примера, определение макроса для dolist можно записать в таком стиле:
Ниже будет больше примеров использования встраиваемых лямбда-списков в defmacro.
Следующим правилом деструктуризации является то, что defmacro позволяет любому лямбда-списку (верхнего уровня или встроенному) быть dotted, заканчивающимся именем параметра. Такая ситуация обрабатывается так, как будто имя параметра в конце списка, стоит после неявного маркера &rest. Например, уже показанное определение dolist может быть записано так:
Если компилятор встречает defmacro, он добавляет новый макрос в окружение компиляции, и также функция раскрытия добавляется в выходной файл. Таким образом новый макрос будет более быстрым во время выполнения. Если необходимо избежать такого механизма, можно использовать defmacro внутри конструкции eval-when.
defmacro может также использоваться для переопределения макроса (например, для установки корректной версии определения вместо некорректной), или для переопределения функции в макрос. Переопределение оператора (смотрите таблицу 5.1) не допускается. Смотрите macrolet, которая устанавливает определение макроса в замкнутом лексическом области видимости.
Допустим, для примера, что необходимо реализовать условную конструкцию аналогичную Fortran’овскому арифметическому выражению IF. (Это конечно требует определённого расширения воображения и приостановки неверия.) Конструкция должна принимать четыре формы: test-value, neg-form, zero-form и pos-form. В зависимости от того, является ли test-form отрицательным, нулём или положительным числом, для выполнения будет выбрана одна из трёх последних форм. С использованием defmacro, определение этой конструкции может выглядеть так:
Необходимо отметить, что в данном определении используется функциональность обратной кавычки (смотрите раздел 22.1.3). Также необходимо заметить, что используется gensym для создания нового имени переменной. Это необходимо для избежания конфликтов с другими переменными, которые могут использоваться в формах neg-form, zero-form или pos-form.
Если форма выполняется интерпретатором, то определение функции для символа arithmetic-if будет является макросом, с которым ассоциирована функция двух аргументом эквивалентная данной
Лямбда-выражение является результатом выполнения декларации defmacro. Вызов list является (гипотетически) результатом макросимвола обратной кавычки (‘) и связанной с ним запятой. Конкретная функция раскрытия макроса может зависеть от реализации, например, она также может содержать проверку на корректность входных аргументов в макровызове.
Теперь, если eval встретит
то раскроет эту форму в
и eval выполнит полученную форму. (Сейчас должно быть понятно, что функциональность обратной кавычки очень полезна для написания макросов. Она используется для построения шаблона, возвращаемой формы, с константными частями и частями для выполнения. Шаблон представляет собой «картину» кода, с местами для заполнения выделенными запятыми.)
Для улучшения примера мы можем сделать так, чтобы pos-form и zero-form могли быть заменены на nil в результате раскрытия макроса. Таким же образом действует и if опуская ветку else в случае истинности условия.
Тогда можно записать
и это раскроется в что-то вроде
Результирующий код корректен, но некрасиво выглядит. Можно переписать определение макроса для генерации лучшего кода, когда pos-form и zero-form могут быть вообще опущены. Или же можно положиться на реализацию Common Lisp’а, которая возможно оптимизирует этот код.
Деструктуризация является очень мощной функциональностью, которая позволяет лямбда-списку в defmacro выразить сложную структуру макровызова. Если не использовались ключевые символы лямбда-списка, то он представляет собой просто список с некоторой степенью вложенности и параметрами в качестве листьев. Структура макровызова должна иметь такую же структуру списка. Например, рассмотрим следующее определение макроса:
Теперь давайте рассмотрим макровызов:
Все это приведёт к тому, что функция раскрытия получит следующие значения для её параметров:
Параметр | Значение |
mouth | m |
eye1 | (car eyes) |
eye2 | (cdr eyes) |
fin1 | f1 |
length1 | (count-scales f1) |
fin2 | f2 |
length2 | (count-scales f2) |
tail | my-favorite-tail |
Следующий макровызов ошибочный, так как аргумента для параметра length1 не представлено:
Следующий макровызов также ошибочный, так как на месте предполагаемого списка указан символ.
Тот факт, что значение переменной my-favorite-head может быть списком, не имеет здесь значения. В макровызове структура должна совпадать с лямбда-списком в определении.
Использование ключевых символов лямбда-списка предоставляет ещё большую гибкость. Например, предположим, что удобно будет в функции раскрытия обращаться к элементам списка, называемым cdmouth, eye1, and eye2, как к head. Можно записать так:
Теперь рассмотрим такой же, как раньше, корректный макровызов:
Это приведёт к тому, что функции раскрытия получит те же значения для своих параметров, а также значение для параметра head:
Параметр | Значение |
head | (m (car eyes) (cdr eyes)) |
Существует условие для деструктуризации, встроенный лямбда-список разрешён только на позиции, где синтаксис лямбда-списка предусматривает имя параметра, но не список. Это защищает от двусмысленности. Например, нельзя записать
потому что синтаксис лямбда-списка не позволяет использовать списки после &optional. Список (a b &rest c) был бы интерпретирован как необязательный параметр a, у которого значение по умолчанию b, и supplied-p параметр с некорректным именем &rest, и дополнительным символом c, также некорректным. Было бы правильнее выразить это так:
Дополнительные круглые скобки устраняют двусмысленность. Однако, такой макровызов, как (loser (car pool)) не предоставляет никакой формы аргумента для лямбда-списка (a b &rest c), значит значение по умолчанию для него будет nil. А так как nil является пустым списком, то этот макровызов ошибочен. Полностью корректное определение выглядит так:
или так
Они слегка отличаются: первое определение требует, что если макровызов явно указывает a, тогда он должен указать явно и b, тогда как второе определение не содержит такого требования. Например,
будет корректным макровызовом для второго определения, но не для первого.