Почему этот макрос Lisp в целом работает, хотя каждый кусок не работает?

Я читаю / работаю через Практический Common Lisp. Я нахожусь в главе о создании тестового фреймворка в Лиспе.

У меня есть функция "test-+", реализованная, как показано ниже, и она работает:

(defun test-+ ()
  (check
    (= (+ 1 2) 3)
    (= (+ 5 6) 11)
    (= (+ -1 -6) -7)))

Помните, я сказал, что это работает, поэтому то, что следует, так сбивает с толку....

Вот код, на который ссылается "test-+":

(defmacro check (&body forms)
  `(combine-results
    ,@(loop for f in forms collect `(report-result ,f ',f))))

(defmacro combine-results (&body forms)
  (with-gensyms (result)
    `(let ((,result t))
       ,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
       ,result)))

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))

(defun report-result (value form)
  (format t "~:[FAIL~;pass~] ... ~a~%" value form)
  value)

Теперь, что я делал, это использовал Slime для макро-расширения, шаг за шагом (используя ctrl-c RET, который сопоставлен с macroexpand-1).

Итак, вызов "check" для "test-+" расширяется до следующего:

(COMBINE-RESULTS
  (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3))
  (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11))
  (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7)))

И тогда этот макрос расширяется до этого:

(LET ((#:G2867 T))
  (UNLESS (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3)) (SETF #:G2867 NIL))
  (UNLESS (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11)) (SETF #:G2867 NIL))
  (UNLESS (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7))
    (SETF #:G2867 NIL))
  #:G2867)

И это тот код, прямо над этим предложением, который не работает. Если я вставлю это в REPL, я получу следующую ошибку (я использую Clozure Common Lisp):

Несвязанная переменная: #:G2867 [Условие типа UNBOUND-VARIABLE]

Теперь, если я возьму тот же код, заменив gensym на имя переменной, такое как "x", он работает просто отлично.

Итак, как мы можем объяснить следующие сюрпризы:

  1. Макрос "test-+", который вызывает все это, работает нормально.

  2. Макро-расширение макроса "комбинировать результаты" не запускается.

  3. Если я уберу слово gensym из макро-расширения "комбинировать результаты", онобудет работать.

Единственное, что я могу предположить, - это то, что вы не можете использовать код, содержащий буквальное использование генсимов. Если так, то почему бы и нет, и как обойти это? И если это не объяснение, что это?

Благодарю.

2 ответа

Решение

Код после распечатки и чтения больше не является тем же кодом. В частности, два случая #:G2867 в печатном представлении будет считываться как два разделенных символа (хотя они и имеют одно и то же имя), в то время как они должны быть одинаковыми в исходном внутреннем представлении.

Попробуйте установить *PRINT-CIRCLE* в T сохранить идентичность в печатном представлении макроразвернутого кода.

GENSYM создает непостоянные символы. Когда макрос работает нормально, это не проблема, потому что один и тот же непостоянный символ подставляется во всем выражении.

Но когда вы копируете и вставляете выражение в REPL, этого не происходит. #: говорит читателю вернуть неустранимый символ. В результате каждое появление #:G2867 это другой символ, и вы получите предупреждение о несвязанной переменной.

Если вы делаете (setq *print-circle* t) перед выполнением MACROEXPAND он будет использовать #n= а также #n# обозначение, чтобы связать идентичные символы вместе.

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