4

While looking to exclude the uses of the list function through the backquote substitutes in defmacros, my attempt failed when combined with the let and `let* recipe, together with the gensym function (as a source of uninterned symbols, as illustrated somewhere else), to create temporary variables - but the latter is not the issue.

A running MWE [EDITED: inserted macroexpand] is the following.

(defun w (a &optional b) ; line 1
  (let ((z (gensym)))
   `(let* ((,z (if (integerp ,b) '((,a ,b)) '((,a))))) ,z)))
    ⇒ w

(eval (w 3)) ; line 2
    ⇒ ((3))
(eval (w 3 0)) ; line 3
    ⇒ ((3 0))

The w is a defun above: when the optional integerp second argument is present, return the pair as a "doubled" list; otherwise return only the first argument, also inside two pairs of parentheses.

Now comes the tricky part: need to replace the defun with a defmacro (m for w).

(defmacro m (a &optional b) ; line 4
  (let ((z (gensym)))
   `(let* ((,z (if (integerp ,b) `((,a ,b)) `((,a))))) ,z)))
    ⇒ m

(macroexpand (m (+ 1 2) 0)) ; line 5
    ⇒ list: Symbol's value as variable is void: a

And also attempt to return the evaluation of (+ 1 2), i.e., ((3 0)). The loop shows the above line #4 is faulty.

Next use instead simple quotes, i.e., '((,a ,b)).

(defmacro m (a &optional b) ; line 6
  (let ((z (gensym)))
   `(let* ((,z (if (integerp ,b) '((,a ,b)) '((,a))))) ,z)))
    ⇒ m

(macroexpand (m (+ 1 2) 0)) ; line 7
    ⇒ (((+ 1 2) 0))

Okay, but there is no evaluation of the argument: not acceptable.

(defmacro m (a &optional b) ; line 8
  (let ((z (gensym)))
   `(let* ((,z (if (integerp ,b) (list (list ,a ,b)) (list (list ,a))))) ,z)))
    ⇒ m

(macroexpand (m (+ 1 2) 0)) ; line 9
    ⇒ ((3 0))

Finally, use the list function for a change: this time it works, yet there is one list too many.

The problem is then how can line #4 keep its minimalistic form using (back)quotes, so that line #5 would result as line #3 - using defmacro versus defun. There is a `(... within `(--- pattern. that might be the cause.

Appreciate all answers. Thank s.

  • 3
    The Right Way to debug and test macros is macroexpand – sds Mar 26 '15 at 19:06
  • Note, you're supposed to quote the argument you're passing to macroexpand, otherwise you're calling macroexpand on the result of evaluating that form. So that would be (macroexpand '(m (+ 1 2) 0)). – Malabarba Mar 26 '15 at 19:45
  • Also, the backquote is very useful, but sometimes you need to ask yourself whether it's really helping or not. In this case, I (personally) think your code would be easier to read if the inner backquote were a regular list. – Malabarba Mar 26 '15 at 19:47
  • @Malabarba: In the context of the "recipe" (the result is eventually set...), the tests with macroexpand need call "on the result of evaluating that form", which is a (let* ((G.... Dropping gensym, by the use of defmacro instead of defun, what could the body of m be other than `(if (integerp ,b) `((,,a ,,b)) `((,,a))) (without two commas and) with "regular lists"? thanks. – sjb Mar 26 '15 at 20:13
  • @sjb No. :-) The result of evaluating that form is one of the branches of the if clause. Roughly put, when a macro form is evaluated, it is first macroexpanded (which yields a (let* ...)) and then the result of the expansion is evaluated (so the let* gets evaluated). Therefore, your call to macroexpand is pointless (you're calling it on something like '((a))). What you want to do when experimenting with macros is call (macroexpand '(m (+ 1 2) 0)). This will expand the form without evaluating it, and will allow you to see what's going on. – Malabarba Mar 26 '15 at 20:24
4

It's not necessary to use a backquote inside another, that's the beauty of it.

(defmacro m1 (a &optional b)
  (let ((z (gensym)))
    `(let ((,z (if (integerp ,b)
                   (list ,a ,b)
                 (list ,a))))
       ,z)))

But that isn't correct yet. The purpose of z isn't to add another variable for the fun of it, but to protect b from being evaluated more than once (because doing that might have side-effects or be costly). You are not doing that. Here's how to do it.

(defmacro m2 (a &optional b)
  (let ((z (gensym)))
    `(let ((,z ,b))
       (if (integerp ,z)
           (list ,a ,z)
         (list ,a)))))

This demonstrates the difference:

(let ((v 1)) (m1 1 (cl-incf v))) => (1 3)
(let ((v 1)) (m2 1 (cl-incf v))) => (1 2)

You do not have to do the same for a because, even though it appears twice, only ever one occurrence is evaluated.

  • Costs when it is over... For now something like m3 (a &optional b z v1 v2) `(let* (,z ,v1 ,v2) (if (integerp ,z) ...) digs for me. Thanks for the correct variant. – sjb Mar 26 '15 at 20:39
2

Nested backquotes require more commas.

(defmacro m (a &optional b) ; line 4
  (let ((z (gensym)))
   `(let* ((,z ,b)) (if (integerp ,z) `((,,a ,,z)) `((,,a))))))
  • 1
    Another possibility is to do: ,`((,a ,b)). – Malabarba Mar 26 '15 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.