Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 744
Charlie Flowers Avatar asked Sep 29 '12 23:09

Charlie Flowers


1 Answers

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.

like image 110
Barmar Avatar answered Sep 27 '22 22:09

Barmar