Почему этот макрос 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", он работает просто отлично.
Итак, как мы можем объяснить следующие сюрпризы:
Макрос "test-+", который вызывает все это, работает нормально.
Макро-расширение макроса "комбинировать результаты" не запускается.
Если я уберу слово gensym из макро-расширения "комбинировать результаты", онобудет работать.
Единственное, что я могу предположить, - это то, что вы не можете использовать код, содержащий буквальное использование генсимов. Если так, то почему бы и нет, и как обойти это? И если это не объяснение, что это?
Благодарю.
2 ответа
Код после распечатки и чтения больше не является тем же кодом. В частности, два случая #:G2867
в печатном представлении будет считываться как два разделенных символа (хотя они и имеют одно и то же имя), в то время как они должны быть одинаковыми в исходном внутреннем представлении.
Попробуйте установить *PRINT-CIRCLE*
в T
сохранить идентичность в печатном представлении макроразвернутого кода.
GENSYM
создает непостоянные символы. Когда макрос работает нормально, это не проблема, потому что один и тот же непостоянный символ подставляется во всем выражении.
Но когда вы копируете и вставляете выражение в REPL, этого не происходит. #:
говорит читателю вернуть неустранимый символ. В результате каждое появление #:G2867
это другой символ, и вы получите предупреждение о несвязанной переменной.
Если вы делаете (setq *print-circle* t)
перед выполнением MACROEXPAND он будет использовать #n=
а также #n#
обозначение, чтобы связать идентичные символы вместе.