1
votes

I've been into Clojure lately and have avoided macros up until now, so this is my first exposure to them. I've been reading "Mastering Clojure Macros", and on Chapter 3, page 28, I encountered the following example:

user=> (defmacro square [x] `(* ~x ~x))
;=> #'user/square
user=> (map (fn [n] (square n)) (range 10))
;=> (0 1 4 9 16 25 36 49 64 81)

The context is the author is explaining that while simply passing the square macro to map results in an error (can't take value of a macro), wrapping it in a function works because:

when the anonymous function (fn [n] (square n)) gets compiled, the square expression gets macroexpanded, to (fn [n] (clojure.core/* n n)). And this is a perfectly reasonable function, so we don’t have any problems with the compiler

This makes sense to me if we assume the body of the function is evaluated before runtime (at compile, or "definition" time) thus expanding the macro ahead of runtime. However, I always thought that function bodys were not evaluated until runtime, and at compile time you would basically just have a function object with some knowledge of it's lexical scope (but no knowledge of its body).

I'm clearly mixed up on the compile/runtime semantics here, but when I look at this sample I keep thinking that square won't be expanded until map forces it's call, since it's in the body of the anonymous function, which I thought would be unevaluated until runtime. I know my thinking is wrong, because if that was the case, then n would be bound to each number in (range 10), and there wouldn't be an issue.

I know it's a pretty basic question, but macros are proving to be pretty tricky for me to fully wrap my head around at first exposure!

3

3 Answers

2
votes

Generally speaking function bodies aren't evaluated at compile time, but macros are always evaluated at compile time because they're always expanded at compile time whether inside a function or not.

You can write a macro that expands to a function, but you still can't refer/pass the macro around as if it were a function:

(defmacro inc-macro [] `(fn [x#] (inc x#)))
=> #'user/inc-macro
(map (inc-macro) [1 2 3])
=> (2 3 4)
1
votes

defmacro is expanded at compile time, so you can think of it as a function executed during compilation. This will replace every occurrence of the macro "call" with the code it "returns".

0
votes

You may consider a macros as a syntax rule. For example, in Scheme, a macros is declared with define-syntax form. There is a special step in compiler that substitutes all the macros calls into their content before compiling the code. As a result, there won't be any square calls in your final code. Say, if you wrote something like

(def value (square 3))

the final version after expansion would be

(def value (clojure.core/* 3 3))

There is a special way to check what will be the body of your macros after being expanded:

user=> (defmacro square [x] `(* ~x ~x))
#'user/square

user=> (macroexpand '(square 3))
(clojure.core/* 3 3)

That's why a macros is an ephemeral thing that lives only in source code but not in the compiled version of it. That's why it cannot be passed as a value or referenced somehow.

The best rule regarding macroses is: try to avoid them until you really need them in your work.