Поведение специальных переменных при макроразложении

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)))
Другие вопросы по тегам