Как это работает, сильно отличается в разных диалектах Лисп.Для Common Lisp он стандартизирован в стандарте ANSI Common Lisp, и различные реализации Common Lisp отличаются в основном от того, используют ли они компилятор, интерпретатор или оба.
В следующем предполагается, что Common Lisp.
EVAL
не переводчик .EVAL может быть реализован с помощью компилятора.Некоторые реализации Common Lisp даже не имеют интерпретатора.Затем EVAL
является вызовом компилятора для компиляции кода, а затем вызывает скомпилированный код.В этих реализациях используется инкрементный компилятор, который может компилировать также простые выражения, такие как 2
, (+ 2 3)
, (gensym)
и т. Д.
Макроразложение выполняется с помощью функций MACROEXPAND
и MACROEXPAND-1
.
Макрос в Common Lisp - это функция, которая ожидает некоторые формы и возвращает другую форму.DEFMACRO регистрирует эту функцию как макрос.
Ваш макрос
(defmacro cube (n)
(let ((x (gensym)))
`(let ((,x ,n))
(* ,x ,x ,x))))
- не что иное, как функция Lisp, которая зарегистрирована как макрос.
Эффект похож наthis:
(defun cube-internal (form environment)
(destructuring-bind (name n) form ; the name would be CUBE
(let ((x (gensym)))
`(let ((,x ,n))
(* ,x ,x ,x)))))
(setf (macro-function 'my-cube) #'cube-internal)
В реальной реализации CL DEFMACRO
расширяется по-разному и не использует имя, подобное CUBE-INTERNAL
.Но концептуально это определение макрофункции и ее регистрация.
Когда компилятор Lisp видит определение макроса, он обычно компилирует макрофункцию и сохраняет ее в текущей так называемой среде.Если среда является средой выполнения, она запоминается во время выполнения.Если среда является средой компилятора при компиляции файла, макрос будет забыт после компиляции файла.Скомпилированный файл должен быть загружен так, чтобы Lisp знал макрос.
Итак, есть побочный эффект при определении макроса и его компиляции.Компилятор запоминает скомпилированный макрос и сохраняет его код.
Когда компилятор теперь видит некоторый код, который использует макрос (cube 10)
, тогда компилятор просто вызывает функцию макроса, которая хранится в текущей среде.под именем CUBE
вызывает эту макрофункцию, которая 10
в качестве аргумента, а затем компилирует сгенерированную форму.Как упоминалось выше, это делается не напрямую, а через функции MACROEXPAND.
Вот определение макроса:
CL-USER 5 > (defmacro cube (n)
(let ((x (gensym)))
`(let ((,x ,n))
(* ,x ,x ,x))))
CUBE
Мы скомпилируем макрос:
CL-USER 6 > (compile 'cube)
CUBE
NIL
NIL
MACRO-FUNCTION
возвращает функцию для макроса.Мы можем вызвать его как любую другую функцию с FUNCALL
.Он ожидает два аргумента: целую форму, такую как (cube 10)
и среду (здесь NIL
).
CL-USER 7 > (funcall (macro-function 'cube) '(cube 10) nil)
(LET ((#:G2251 10)) (* #:G2251 #:G2251 #:G2251))
Также можно взять функцию (которая принимает два аргумента: форму и среду) и сохраните его, используя SETF в качестве макрофункции.
Сводка
Когда работает компилятор Common Lisp, он просто знает функции макроса и при необходимости вызывает их для расширениякод через встроенный макроэкспандер.Макро-функции - это просто код на ЛиспеКогда компилятор Lisp видит определение макроса, он компилирует функцию макроса, сохраняет ее в текущей среде и использует ее для расширения последующего использования макроса.
Примечание. В Common Lisp необходимо, чтобы макросопределяется до того, как он может быть использован компилятором.