Clayton Stanley
Clayton Stanley

Reputation: 7784

Proper backquoting and variable capture with labels+macrolet within defmacro

I'm writing a small special-purpose language in Common Lisp using a defmacro. I cannot figure out the proper backquote procedure to have a variable defined in a top-level let statement, shadowed in a nested macrolet, and returned in a nested labels, all point to the same symbol.

Here's some small example code that shows the problem:

(defmacro with-keylang (&body body)
  (let ((keyblank-size-x (gensym)))
    `(let ((,keyblank-size-x 42))
       (labels ((keyblank-size-x ()
                  ,keyblank-size-x))
         (macrolet ((with-keyblank-size-x (size-x &body body)
                      `(let ((,',keyblank-size-x ,size-x))
                         ,@body)))
           ,@body)))))

CL-USER> 
(with-keylang
  (print (keyblank-size-x)))

42  
42

All is well so far.

CL-USER> 
(with-keylang
  (with-keyblank-size-x 24
    (print (keyblank-size-x))))

;Compiler warnings :
;   In an anonymous lambda form: Unused lexical variable #:G123171

42 
42

There's the problem. I want the symbol representing keyblank-size-x to be shadowed with the value 24, and this is not happening.

I have a feeling that the ,', backquote pattern isn't proper for this case, as this quotes the symbol representing keyblank-size-x, and is therefore not eq. But if I try ,,, it doesn't work, and I get this interesting compiler error:

While compiling WITH-KEYBLANK-SIZE-X :
Illegal reference to lexically defined variable #:G123192.
   [Condition of type CCL::COMPILE-TIME-PROGRAM-ERROR]

EDIT:

The keyblank-size-x variable was lexically scoped, and I wanted dynamic scope for this particular case. So here's the rewrite declaring the keyblank-size-x variable to have dynamic scope:

(defmacro with-keylang (&body body)
  (let ((keyblank-size-x (gensym)))
    `(let ((,keyblank-size-x 42))
       (declare (special ,keyblank-size-x))
       (labels ((keyblank-size-x ()
                  ,keyblank-size-x))
         (macrolet ((with-keyblank-size-x (size-x &body body)
                      `(let ((,',keyblank-size-x ,size-x))
                         (declare (special ,',keyblank-size-x))
                         ,@body)))
           ,@body)))))

And the test code:

CL-USER>
(with-keylang
  (with-keyblank-size-x 25
    (with-keyblank-size-x 21
      (print (keyblank-size-x)))
    (print (keyblank-size-x)))
  (print (keyblank-size-x)))

21
25
42
42

Upvotes: 2

Views: 306

Answers (1)

Rainer Joswig
Rainer Joswig

Reputation: 139261

If we fully expand the code (here using the Walk command in LispWorks):

(LET ((#:G19508 42))
  (LABELS ((KEYBLANK-SIZE-X () #:G19508))
    (MACROLET ((WITH-KEYBLANK-SIZE-X (SIZE-X &BODY BODY)
                 `(LET ((#:G19508 ,SIZE-X))
                    ,@BODY)))
      (LET ((#:G19508 24))
        (PRINT (KEYBLANK-SIZE-X))))))

Rebinding #:G19508 has no effect and can't have. The function keyblank-size-x has a different lexical binding. This is just the usual effect of lexical binding.

Upvotes: 4

Related Questions