Поведение специальных переменных при макроразложении
FUZZ> (defvar *foo* nil)
*FOO*
FUZZ> (defmacro bar ()
(format t "foo: ~A" *foo*)
`(+ 1 1))
BAR
FUZZ> (defmacro bot ()
(let ((*foo* 17))
`(bar)))
BOT
FUZZ> (bot)
foo: NIL
Моя ментальная модель (явно неправильная) макроподключения говорит, что по порядку происходит следующее:
Запустите расширение макроса bot
(который связывает *foo*
в 17
), запустите расширение макроса bar
, который печатает текущее значение *foo*
(будучи 17
) и возвращает форму (+ 1 1)
, который не является макросом, время расширения макроса прошло, наконец, оцените форму (+ 1 1)
и возвращает 2
,
Почему я не прав?
Есть ли простой способ сделать то, что я намерен?
1 ответ
Когда REPL сказано оценить (bot)
Сначала необходимо выполнить макроразложение. Вызывает функцию макроразложения bot
что означает, по сути, оценку
(let ((*foo* 17))
`(bar))
Что возвращает (bar)
а затем связывание из let
разматывается. Теперь у нас есть (bar)
, bar
является макросом, так что пришло время для другого раунда макроразложения, что означает оценку
(progn
(format t "foo: ~a" *foo*)
`(+ 1 1))
который печатает foo: NIL
и возвращает (+ 1 1)
,
Если вы хотите, чтобы макроразложение выполнялось в рамках некоторых привязок, вам нужно вызвать функцию макроразложения самостоятельно. Например, вы можете использовать macroexpand:
CL-USER> (defparameter *foo* nil)
*FOO*
CL-USER> (defmacro bar ()
(format t "foo: ~a" *foo*)
`(+ 1 1))
BAR
CL-USER> (defmacro baz ()
(let ((*foo* 42))
(macroexpand '(bar))))
BAZ
CL-USER> (baz)
foo: 42
2
Но, если вы собираетесь делать макроразложение самостоятельно, обязательно сохраняйте аргументы окружения. В этом случае лучшее определение baz
было бы:
(defmacro baz (&environment env)
(let ((*foo* 42))
(macroexpand '(bar) env)))