Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly use syntax-quote and unquote inside of `defmacro`

Tags:

macros

clojure

I have a simple macro:

(defmacro macrotest [coll]
  `(let [result# ~(reduce + coll)]
         result#))

Why, if this code works:

(macrotest [1 2 3])

doesn't this code work?

(def mycoll [1 2 3])
(macrotest mycoll)
like image 781
Ribelo Avatar asked Jun 22 '14 22:06

Ribelo


1 Answers

Symbols are not the same as the value they point to.

One of the important considerations about macros is not just how they create new code, but also how they handle the arguments you pass in.

Consider this:

(def v [1 2 3])

(somefunction v)

The argument v passed to this function does not arrive inside the function as a symbol, v. Instead, because this is a function call, the arguments are evaluated first, then their resulting value is passed into the function. So the function will see [1 2 3].

But macros are not like this, though it is easy to forget. When you call:

(somemacro v)

v is not evaluated and does not pass in [1 2 3]. Inside the macro, all you get is a symbol, v. Now, your macro can emit the symbol you passed in, in the code the macro creates, but it cannot do anything with the value of v unless you add an extra level of evaluation using eval (see footnote -- not recommended).

When you unquote the macro argument, you get a symbol, not a value.

Consider this example:

user> (def a 5)
#'user/a
user> (defmacro m [x] `(+ ~x ~x))
#'user/m
user> (m a)
10
user> (macroexpand '(m a))
(clojure.core/+ a a)
user> (defmacro m [x] `~(+ x x))
#'user/m
user> (m a)
ClassCastException clojure.lang.Symbol cannot be cast to java.lang.Number  clojure.lang.Numbers.add (Numbers.java:126)

This would be like trying to do this at the REPL: (+ 'a 'a) Adding symbols, not adding the values those symbols point to.

You might look at these two different definitions of the macro m and think they are doing the same thing. But in the second one, there is an attempt to do something with the symbol x, to do addition with it. In the first macro, the compiler is not asked to do anything with x. It merely is told to emit the addition operation, which will then be actually processed at runtime.

Remember that the point of a macro is to create code not perform runtime activity that a function would do. The code that is created is then immediately run (via implicit eval), so it's easy to forget this separation, but they are two distinct steps.

As a final exercise, pretend you are a compiler. I want you to tell me right now what is the resulting value of this code:

(* t w)

Well? It's impossible. There is no way you can tell me the answer to this question because you have no idea what t and w are. However, later, at runtime, when these presumably have a value, then it will be easy.

And that's what macros do: they output stuff for the runtime to handle.


(Very much not recommended but to help further describe what's happening, this works:)

user> (def a 1)

user> (defmacro m [x] `~(+ (eval x) (eval x)))

user> (m a)

2

like image 112
johnbakers Avatar answered Nov 15 '22 07:11

johnbakers