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.

Drew
  • 75,699
  • 9
  • 109
  • 225
sjb
  • 101
  • 7
  • 3
    The Right Way to debug and test macros is [`macroexpand`](http://www.gnu.org/software/emacs/manual/html_node/elisp/Expansion.html) – 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
  • @Malabarba Yes. ;-) Put `'(m (+ 1 2) 0)` and see only dots. – sjb Mar 26 '15 at 20:36
  • I don't understand what you mean by that. Using the macro defined in your answer (since that's the working version), if I call `(macroexpand '(m (+ 1 2) 0))` I get the following ```(let* ((G89271 (if (integerp 0) (\` (((\, (+ 1 2)) (\, 0)))) (\` (((\, (+ 1 2)))))))) G89271)```, which is exactly what I would expect to get. – Malabarba Mar 26 '15 at 20:50
  • `(let* ((G45287 (if ... ... ...))) G45287)` – sjb Mar 26 '15 at 21:06
  • Put your cursor on that and press enter, it will fully expand. – Jordon Biondo Mar 27 '15 at 13:24

2 Answers2

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.

tarsius
  • 25,298
  • 4
  • 69
  • 109
  • 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
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))))))
sjb
  • 101
  • 7