Jindřich Mynarz
Jindřich Mynarz

Reputation: 1613

Clojure macros: resolving vars

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))

Upvotes: 2

Views: 342

Answers (2)

coredump
coredump

Reputation: 38967

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.

Upvotes: 2

Shlomi
Shlomi

Reputation: 4756

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".

Upvotes: 3

Related Questions