user2853797
user2853797

Reputation: 5

Confusing clojure macro var evaluation

I am trying to write a clojure macro that transforms clojure keywords to java enums. But the evaluation of the parameters in a macro is confusing:

user=> (defmacro show-and-tell [thing]
#_=>   `(vector ~(name thing) ~(type thing) ~thing))
#'user/show-and-tell
user=> (macroexpand-1 (show-and-tell :foo))
["foo" clojure.lang.Keyword :foo]
user=> (def foo :bar)
#'user/foo
user=> (name foo)
"bar"
user=> (type foo)
clojure.lang.Keyword
user=> (macroexpand-1 (show-and-tell foo))
["foo" clojure.lang.Symbol :bar]

So it works as I would expect if the keyword is provided directly as a parameter. But if it is a var, I don't get the correct name and type of the thingy.

I can make it 'sort-of-almost' work by using eval:

user=> (defmacro show-and-tell-with-eval [thing]
  #_=>   `(vector ~(name (eval thing)) ~(type (eval thing)) ~(eval thing)))
#'user/show-and-tell-with-eval
user=> (macroexpand-1 '(show-and-tell-with-eval foo))
(clojure.core/vector "bar" clojure.lang.Keyword :bar)
user=> (let [baz :bar-foo] (macroexpand-1 '(show-and-tell baz)))
(clojure.core/vector "baz" clojure.lang.Symbol baz)
user=> (let [baz :bar-foo] (macroexpand-1 '(show-and-tell-with-eval baz)))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: baz in this context

Can someone explain this to me? Is there no way to see the name of a (local) var inside a macro?

Upvotes: 0

Views: 383

Answers (2)

schaueho
schaueho

Reputation: 3504

You seem to be confused about the relation between vars and what they contain and how macros come into play. "Vars provide a mechanism to refer to a mutable storage location" (cf. the offical docs on Vars). When you evaluate foo in the REPL, Clojure will evaluate it according to the rules outlined in the offical docs for evaluation which in this case means, it resolves the symbol to "the value of the binding of the global var named by the symbol".

Now, it's crucial to understand that macros "are functions that manipulate forms, allowing for syntactic abstraction". Basically macros allow direct access to any parameter handed to the macro and then manipulate and evaluate the related data as needed. Let's take a glance at your macro and what happens in your "erroneous" case:

(defmacro show-and-tell [thing]
  `(vector ~(name thing) ~(type thing) ~thing))

Your macro definition gets fed some thing (whatever is the parameter to show-and-tell). At this point, thing will not be resolved. Only in your macro definition you have some evaluations. Please note that in this call, you're calling macroexpand-1 on the (evaluated) result of (show-and-tell foo) which is probably not what you want:

user=> (macroexpand-1 (show-and-tell foo))
["foo" clojure.lang.Symbol :bar]

Quoting the call shows what's going on:

user=> (macroexpand-1 '(show-and-tell foo))
(clojure.core/vector "foo" clojure.lang.Symbol foo)

You're calling vector with "foo" (i.e. the name of foo) where foo is a symbol and then your code will resolve foo normally (and give :bar).

From your description, you seem to expect that normal symbol resolution will take place for all your arguments. If this is what you want, you don't need a macro in the first place. But just for the record, by now it should be obvious what you need to do: you need to evalute thing first (which is pretty much what you did with eval). In other words, you placed your unquote operator wrong:

(defmacro show-and-tell [thing]
  `(vector (name ~thing) (type ~thing) ~thing))
user=> (macroexpand-1 '(show-and-tell foo))
(clojure.core/vector (clojure.core/name foo) (clojure.core/type foo) foo)
user=> (show-and-tell foo)
["bar" clojure.lang.Keyword :bar]

Upvotes: 1

Jaime Agudo
Jaime Agudo

Reputation: 8296

You might wanted to write

(defmacro show-and-tell [thing] `(vector (name ~thing) (type ~thing) ~thing))

Ad-hoc explanation:

The key to understand what's going on is to know when the arguments are evaluated. Macros take unevaluated data structures as arguments and return a data structure which is then evaluated using the rules above. Using the ~ you tell the compiler which data structures should be evaluated at runtime, thus, your thing argument, not the return value of (name thing) value as the thing value will be bound at compile time in the latter case which is not what you wanted

Here you have a further explanation about writing macros http://www.braveclojure.com/writing-macros/

Upvotes: 2

Related Questions