Как установить привязку переменной, которая будет активна во время макроразвлечения?
Давайте определим функцию, тело которой содержит макрос, который будет раскрыт в неустановленное время и будет использовать глобальное динамическое значение *test*
во время этого процесса.
> (defvar *test* nil)
> (defmacro body ()
`(print ,*test*))
> (defun test ()
(body))
> (test)
NIL
Но что, если я хочу связать *test*
сказать, 1
во время определения функции, чтобы макроразложение работало с этой привязкой в действии и вызовом test
произведенный 1
вместо NIL
,
Просто упаковка defun
в let
не работает:
> (let ((*test* 1))
(defun test ()
(body)))
> (test)
NIL
Возможно, это связано с этой строкой в Hyperspec:
defun не требуется для выполнения каких-либо побочных эффектов во время компиляции
Но есть ли другие способы сделать это?
4 ответа
Как вы сами пишете, макросы раскрываются в неуказанное время. В моем SBCL макрос раскрывается до того, как оценивается вся форма, что означает, что до вступления в силу привязки LET. Для некоторых интерпретаторов макрос может быть расширен при выполнении функции после истечения срока привязки.
Ранние версии того, что стало Common Lisp, включали такой механизм через COMPILER-LET, но он был удален. См. Проблему COMPILER-LET-CONFUSION для более подробной информации. Лексически некоторые из эффектов могут быть достигнуты с помощью MACROLET/SYMBOL-MACROLET. Динамически это трудно сделать разумно, и я бы рекомендовал переосмыслить подход, если использование реальных динамических привязок кажется необходимым.
Вы могли бы представить let
как это:
(defvar *test* nil)
(defmacro foo ()
(let ((test (gensym)))
`(let ((,test *test*))
(print ,test))))
(defun test-foo ()
(foo))
(test-foo) => print and returns NIL
(let ((*test* 1))
(test-foo)) => print and returns 1
Как насчет управления временем оценки с помощью макроса вместо let (здесь он присваивает известное значение известной переменной, но его можно легко расширить для обработки большего количества переменных, поскольку мы играем с динамическими переменными):
(defmacro letter (&body body)
(let ((old-test *test*))
(set '*test* 1)
`(progn
,@body
(set '*test* ,old-test))))
определяющий test
:
(letter (defun test () (body)))
С помощью test
:
CL-USER> (test)
1
1
Похоже, что это работает, как и ожидалось на SBCL, нужно немного поспать, прежде чем пробовать это на других реализациях.
Хм, макро-расширение делает очевидным, что letter
работает правильно только при макро-расширении и EVALed. Простое расширение макроса не восстанавливает *test*
к его старому значению (до). Так что это не хороший "эмулятор привязки".
Я думаю это потому что *test*
переменная действительна в теле let
,