5

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.

9
  • 3
    The Right Way to debug and test macros is macroexpand
    – sds
    Mar 26, 2015 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, 2015 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, 2015 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, 2015 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, 2015 at 20:24

2 Answers 2

5

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.

1
  • 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, 2015 at 20:39
3

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
  • 1
    Another possibility is to do: ,`((,a ,b)).
    – Malabarba
    Mar 26, 2015 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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