0

The following implementation of with-gensyms, intended to operate on a let-like argument list, generates an extra comma within the nested let:

(defmacro with-gensyms (vars &rest forms)
  `(let ,(loop for var in vars
          collect (if (listp var)
                      `(,(car var) (gensym))
                      `(,var (gensym))))
     `(let ,,(loop for genvar in vars
              collect (if (listp genvar)
                          `(,(car genvar) ,(car (cdr genvar)))
                          `(,genvar nil)))
        ,,@forms)))

An example macroexpansion:

(with-gensyms ((var1 1) (var2 2) (testvar "testname") the-number-zero))

(LET ((VAR1 (GENSYM))
      (VAR2 (GENSYM))
      (TESTVAR (GENSYM))
      (THE-NUMBER-ZERO (GENSYM)))
  `(LET ,((VAR1 1) (VAR2 2) (TESTVAR "testname") (THE-NUMBER-ZERO NIL))))

There's an extra comma before the argument list for the nested let, and it's not obvious to me why this is the case.

The target output is the following:

(let ((var1 (gensym)) (var2 (gensym)) (testvar (gensym)) (the-number-zero (gensym)))
  `(let ((,var1 1) (,var2 2) (,testvar "testname") (,the-number-zero nil))))

Any help would be nice!

hsg
  • 1
  • The backtick acts as a quote. Literals follow. Don't process these... The comma unquotes that, so that evaluation can begin again. https://stackoverflow.com/questions/6474777/common-lisp-backquote-backtick-how-to-use - https://stackoverflow.com/questions/18515295/why-isnt-there-an-unquote-lisp-primitive - the extra comma is because it has to unquote both of those. They're wrapped in that first parenthesis as well. – Doyousketch2 Jul 04 '21 at 07:18
  • I see what you're trying to do, but this is very hard. I think the more pressing problem is that there is _no_ comma before e. g. `var1` in the inner bindings. You need to reference the generated symbol of the outer bindings here, but there is no clear way to do that, although I am maybe overlooking something. – Svante Jul 06 '21 at 22:48
  • Yeah, the problem is that you don't have the generated symbol in hand at macro expansion time. You need to eliminate the idea of generating the generation code, and instead do it directly. – Svante Jul 06 '21 at 23:01

1 Answers1

0

That deserves a better explaination than just a note. Some people might get confused by that. You have to keep track of which parenthesis have been opened, and which have been closed. Line up any parenthesis-pair that doesn't close on the same line, and it's easier to explain.

(defmacro with-gensyms (vars &rest forms)

;; v-- this backticked-parenthesis has been opened here.

  `(let ,(loop for var in vars
          collect (if (listp var)
                      `(,(car var) (gensym))
                      `(,var (gensym))
                  )
         )

;; ^-- but it hasn't been closed yet.
;;      so we now have to unquote for that one...
;;   v-- as well as this new one.

     `(let ,,(loop for genvar in vars
              collect (if (listp genvar)
                          `(,(car genvar) ,(car (cdr genvar)))
                          `(,genvar nil)
                      )
             )
        ,,@forms

      )  ;;  second backtick closes here.

   )  ;;  first backtick closes here.

)
Doyousketch2
  • 2,060
  • 1
  • 11
  • 11
  • Hey, thanks! The extra comma in the actual implementation makes sense to me, but not the comma before the nested let in the output for the macro; see the macroexpansion for an example of the bug. – hsg Jul 04 '21 at 16:15