Why does this Lisp macro as a whole work, even though each piece doesn't work?

若如初见. 提交于 2019-12-10 13:13:20

问题


I'm reading/working through Practical Common Lisp. I'm on the chapter about building a test framework in Lisp.

I have the function "test-+" implemented as below, and it works:

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

Remember, I said, it works, which is why what follows is so baffling....

Here is some code that "test-+" refers to:

(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)

Now, what I've been doing is using Slime to macro-expand these, step by step (using ctrl-c RET, which is mapped to macroexpand-1).

So, the "check" call of "test-+" expands to this:

(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)))

And then that macro-expands to this:

(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)

And it is THAT code, directly above this sentence, which doesn't work. If I paste that into the REPL, I get the following error (I'm using Clozure Common Lisp):

Unbound variable: #:G2867 [Condition of type UNBOUND-VARIABLE]

Now, if I take that same code, replace the gensym with a variable name such as "x", it works just fine.

So, how can we explain the following surprises:

  1. The "test-+" macro, which calls all of this, works fine.

  2. The macro-expansion of the "combine-results" macro does not run.

  3. If I remove the gensym from the macro-expansion of "combine-results", it does work.

The only thing I can speculate is that you cannot use code the contains literal usages of gensyms. If so, why not, and how does one work around that? And if that is not the explanation, what is?

Thanks.


回答1:


The code, after being printed and read back, is no longer the same code. In particular, the two instances of #:G2867 in the printed representation would be read back as two separated symbols (albeit sharing the same name), while they should be the same in the original internal representation.

Try setting *PRINT-CIRCLE* to T to preserve the identity in the printed representation of the macro-expanded code.




回答2:


GENSYM creates uninterned symbols. When the macro runs normally, this isn't a problem, because the same uninterned symbol is being substituted throughout the expression.

But when you copy and paste the expression into the REPL, this doesn't happen. #: tells the reader to return an uninterned symbol. As a result, each occurrence of #:G2867 is a different symbol, and you get the unbound variable warning.

If you do (setq *print-circle* t) before doing the MACROEXPAND it will use #n= and #n# notation to link the identical symbols together.



来源:https://stackoverflow.com/questions/12657481/why-does-this-lisp-macro-as-a-whole-work-even-though-each-piece-doesnt-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!