2

I can seem to wrap my head around macros in Clojure. I'm probably missing something basic. First, allow me to describe an example of what I want.

(defmacro macrotest [v] `(d ...))
(def a '(b c))
(macroexpand-1 '(macrotest a))
; => (d (b c))

In other words, the var passed to macrotest is resolved, but not evaluated further.

Providing the macro with the value of the var works:

(defmacro macrotest [v] `(d ~v))
(macroexpand-1 '(macrotest (b c)))
; => (d (b c))

But providing the var does not:

 (def a '(b c))
 (macroexpand-1 '(macrotest a))
 ; => (d a)

Is it possible to resolve a var in Clojure macro, but not evaluate its value?

EDIT: What I want seems to be possible with eval:

  (defmacro macrotest [v] `(d ~(eval v)))
  (def a '(b c))
  (macroexpand-1 '(macrotest a))
  ; => (user/d (b c))
Jindřich Mynarz
  • 1,563
  • 1
  • 16
  • 31

2 Answers2

2

Its important to understand that macros evaluate at compile time, not at runtime.

at compile time var a does not yet have a value, so its value is not passed to the macro, only its name. However, when you explicitly pass the "value" - well, then it exists at compile time, and you see that it "works".

Shlomi
  • 4,708
  • 1
  • 23
  • 32
  • Thanks. I know that macros are evaluated at compile time, but I probably don't understand all implications of this. – Jindřich Mynarz Jun 27 '16 at 08:19
  • So the answer to my last question is "no"? There is no way of passing runtime values into macros? – Jindřich Mynarz Jun 27 '16 at 08:47
  • 1
    I dont think there is a way, other than maybe `eval` and such. In lisps, macros are usually used to build new "syntax", so if you find that you really must have a runtime value at compile time, perhaps you should reconsider what you are trying to achieve ;) – Shlomi Jun 27 '16 at 09:06
  • Would it be possible if the macro expanded to a function? – Jindřich Mynarz Jun 27 '16 at 09:39
  • Using `eval` seems to work for my use case (see the edited post). I'm not sure though if it's a good idea. – Jindřich Mynarz Jun 27 '16 at 10:08
2

Lexical environment

Using eval inside a macro is bad practice. Sure, the following works:

(macroexpand-1 '(macrotest a))
; => (user/d (b c))

... but then, this does not work as expected:

(macroexpand-1 '(let [a "oh no"]
                  (macrotest a)))
; => (user/d (b c))

Surely you would like the macro to evaluate a as it was defined locally, not using the global variable bound to it, but you cannot because the macro does not evaluate v in the right lexical context. And this is the key to understanding macros: they take code and produce code; any data manipulated by that code is not available to you yet. In other words, the moment at which macros are expanded might be totally unrelated to the time their resulting code is evaluated: anything you evaluate while a macro is being executed is probably evaluated too soon w.r.t. the relevance of your data.

What do you want to do?

There is a form named macrotest which should accepts an argument, v and then perform as-if you had form d applied to it. But:

  • (macrotest (b c)) should not evaluate (b c), just copy it untouched to produce (d (b c)).
  • (macrotest a) should evaluate a, which results in a quoted form, and place that form in the resulting code, also returning (d (b c)).

You have a problem here, because the meaning changes radically in one or the other case. Either you evaluate an argument, in which case you must quote (b c) and you write:

(defmacro macrotest [v] `(d ~v))

... which requires that d is ready to handle quoted arguments; or you are okay with the argument being not evaluated. With the same d as above, you can quote the argument explicitly:

(defmacro macrotest [v] `(d '~v))

However, if d itself is a macro that does not evaluate its argument, you have to avoid the quote in front of ~v.

coredump
  • 37,664
  • 5
  • 43
  • 77
  • 1
    Excellent answer! It's true that I forgot about the scope of the vars used in the macro. However, I have doubts about quoting in the second part of your answer. If I use `(defmacro macrotest [v] `(d '~v))` and `(def a '(b c))`, then `(macrotest a)` expands to `(d (quote a))` instead of `(d (b c))`. – Jindřich Mynarz Jun 28 '16 at 11:37
  • 1
    @jindrichm Thanks, and about the last part you are right, I was probably not very clear. The intent here is that in the second case `macrotest` does not evaluate its argument at all and works only with unquoted literal data; then, that data is quoted and passed to `d`. Consequently, giving `a` to it would not evaluate the expression (as most macros do). – coredump Jun 28 '16 at 12:21