8

I am trying to call a macro (defined somewhere else; I can't change it), but I want to evaluate some of the arguments before the macro is expanded. Is this possible?

For example:

(defmacro foo (&rest args)
  (mapcar (lambda (a) (message "arg: %s" a)) args) nil)

(foo b (+ 1 1) c)
;; Prints:
;;   arg: b
;;   arg: (+ 1 1)
;;   arg: c
;; But I want:
;;   arg: b
;;   arg: 2
;;   arg: c

The only way I've found so far is:

(eval `(foo b ,(+ 1 1) c))

Is there a better way?

Drew
  • 75,699
  • 9
  • 109
  • 225
0x5453
  • 319
  • 1
  • 7
  • 4
    1. There's no way to ask an existing macro to evaluate its arguments when you call it (not doing so is one of the key properties of a macro). 2. Your example macro expands to `nil` and would trigger those messages only at expansion-time, so... that's weird. – phils May 15 '19 at 21:41
  • I agree that it's a bit weird. For context, I'm messing around with Doom Emacs' [`doom!`](https://github.com/hlissner/doom-emacs/blob/develop/init.example.el) macro, which I assume was written as such to avoid quoting all of its arguments. I'm trying to make some of my included packages conditional based on whether I am configured for running at work, at home, etc., but `doom!` is interpreting my `(when ...)` forms as package declarations. The `eval` trick above seems to hack around that. – 0x5453 May 16 '19 at 13:30
  • 4
    How about `(let ((tmp (+ 1 1))) (foo b tmp c))`? – kdb May 16 '19 at 22:24

1 Answers1

2

This is completely doable. Since a macro operates on lists, your parameters are similarly going to be a list. You can check if any of the elements in the argument list are lists themselves, and evaluate them:

(defmacro foo (&rest args)
  (mapcar
   #'(lambda (a)
       (message "arg: %s" (if (listp a) (eval a) a)))  ; Changed here...
   args)
  nil)

For clarity, I've added some spacing. Now, when I run this:

(foo b (+ 1 1) c)

... it shows:

arg: b
arg: 2
arg: c

Your addition example is trivial, and your real code is probably more complicated, so beware. This makes the potential for some instability if you want to pass in a list for some parameter. I suppose you could get even fancier, and also check if the car of the list is a function, and so on as needed.

cyberbisson
  • 887
  • 1
  • 6
  • 17