Как вы, вероятно, знаете, составные формы Lisp обычно обрабатываются извне. Вы должны взглянуть на символ в первой позиции самой внешней вложенности, чтобы понять форму.Этот символ полностью определяет значение формы.
;; Common Lisp: define a class A derived from B and C
(defclass a (b c) ())
;; Common Lisp: define a function of two arguments
(defun a (b c) ())
;; add A to the result of calling function B on variable C:
(+ a (b c))
Традиционно диалекты Лисп делят формы на операторные формы и формы вызова функций.Форма оператора имеет совершенно произвольное значение, определяемое фрагментом кода, который компилирует или интерпретирует эти функции (например, оценка просто повторяется по всем формам аргументов вызова функции, а полученные значения передаются в функцию).
С ранней истории Lisp позволял пользователям писать своих собственных операторов.Существовали два подхода к этому: интерпретирующие операторы (исторически известные как fexprs
) и операторы компиляции, известные как макросы.Обе они основаны на идее функции, которая получает в качестве аргумента неоцененную форму, чтобы она могла реализовать собственную стратегию, расширяя тем самым модель оценки новыми поведениями.
Оператор типа fexpr
просто вручаетсяформа во время выполнения, вместе с объектом среды, с помощью которого он может искать значения переменных и тому подобное.Затем этот оператор обходит форму и реализует поведение.
Макрооператору передается форма во время расширения макроса (что обычно происходит при чтении форм верхнего уровня, непосредственно перед их оценкой или компиляцией).Его задача не в том, чтобы интерпретировать поведение формы, а в том, чтобы переводить ее, генерируя код.Т.е. макрос - это мини-компилятор.(Сгенерированный код может содержать больше вызовов макросов; об этом позаботится макроэкспандер, гарантируя, что все вызовы макросов прорежены.)
Подход fexpr
потерял популярность, скорее всего потому, что он неэффективен.В основном это делает компиляцию невозможной, тогда как Lisp-хакеры ценят компиляцию.(Lisp уже был скомпилированным языком примерно в 1960 году.) Подход fexpr
также враждебен лексической среде;для этого требуется fexpr
, являющаяся функцией, чтобы иметь возможность вглядываться в среду привязки переменных в той форме, в которой она была вызвана, что является своего рода нарушением инкапсуляции, которое не допускается лексическими областями.
Макроспись немного сложнее и в некоторых отношениях менее гибкая, чем fexprs, но поддержка написания макросов улучшалась в Лиспе с 1960-х по 70-е годы, чтобы сделать его максимально простым.Макрос изначально должен был получить всю форму, а затем должен был разобрать ее самостоятельно.Макроопределяющая система превратилась во что-то, что предоставляет макро-функции с аргументами, которые получают разбитый синтаксис в легко усваиваемых частях, включая некоторые вложенные аспекты синтаксиса.Был также разработан синтаксис обратных кавычек для написания шаблонов кода, который значительно упрощает процесс генерации кода.
Итак, чтобы ответить на ваш вопрос, как я могу сам писать такие формы?Например, если:
;; Imitation of old-fashioned technique: receive the whole form,
;; extract parts from it and return the translation.
;; Common Lisp defmacro supports this via the &whole keyword
;; in macro lambda lists which lets us have access to the whole form.
;;
;; (Because we are using defmacro, we need to declare arguments "an co &optional al",
;; to make this a three argument macro with an optional third argument, but
;; we don't use those arguments. In ancient lisps, they would not appear:
;; a macro would be a one-argument function, and would have to check the number
;; of arguments itself, to flag bad syntax like (my-if 42) or (my-if).)
;;
(defmacro my-if (&whole if-form an co &optional al)
(let ((antecedent (second if-form)) ;; extract pieces ourselves
(consequent (third if-form)) ;; from whole (my-if ...) form
(alternative (fourth if-form)))
(list 'cond (list antecedent consequent) (list t alternative))))
;; "Modern" version. Use the parsed arguments, and also take advantage of
;; backquote syntax to write the COND with a syntax that looks like the code.
(defmacro my-if (antecedent consequent &optional alternative)
`(cond (,antecedent ,consequent) (t ,alternative))))
Это подходящий пример, потому что изначально Lisp имел только cond
.Не было if
в Лиспе Маккарти.Этот «синтаксический сахар» был изобретен позже, вероятно, как макрос, расширяющийся до cond
, точно так же, как my-if
выше.