Как установить привязку переменной, которая будет активна во время макроразвлечения?

Давайте определим функцию, тело которой содержит макрос, который будет раскрыт в неустановленное время и будет использовать глобальное динамическое значение *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,

Другие вопросы по тегам