Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between macroexpand and macroexpand-1 in Clojure

I couldn't understand the difference between macroexpand and macroexpand-1.

Could you provide examples?

like image 313
Ertuğrul Çetin Avatar asked May 05 '16 10:05

Ertuğrul Çetin


People also ask

Why use macros Clojure?

Macros are useful for writing new, convenient syntactic constructs. And when we talk about syntax, we are typically talking about raw, unevaluated sexpressions. clojure. core/when is a syntactic sugar macro which transforms into an if with a do for a then and no else.

Does Clojure have macros?

Clojure has a programmatic macro system which allows the compiler to be extended by user code. Macros can be used to define syntactic constructs which would require primitives or built-in support in other languages. Many core constructs of Clojure are not, in fact, primitives, but are normal macros.


2 Answers

Let's say we have the following code:

(defmacro inner-macro [arg]
  `(println ~arg))

(defmacro top-level-macro [arg]
  `(inner-macro ~arg))

(defn not-a-macro [] nil)

Then, doc of macroexpand-1 says:

If form represents a macro form, returns its expansion, else returns form.

Indeed, it does:

user> (macroexpand-1 '(inner-macro "hello"))
(clojure.core/println "hello")

user> (macroexpand-1 '(top-level-macro "hello"))
(user/inner-macro "hello")

user> (macroexpand-1 '(not-a-macro))
(not-a-macro)

In other words, macroexpand-1 does just one step of macroexpansion if supplied form is a macro form.

Then, doc of macroexpand:

Repeatedly calls macroexpand-1 on form until it no longer represents a macro form, then returns it.

Example:

user> (macroexpand '(top-level-macro "hello"))
(clojure.core/println "hello")

What happened? As soon as, (top-level-macro "hello") expands to (user/inner-macro "hello"), which is macro form, macroexpand will perform expansion once again. The result of second expansion is (clojure.core/println "hello"). It is not a macro form, so macroexpand just returns it.

So, just to rephrase the doc, macroexpand will recursively do expansion until top level form is not a macro form.

Also there's additional note in macroexpand's doc:

Note neither macroexpand-1 nor macroexpand expand macros in subforms.

What does that mean? Let's say we have one more macro:

(defmacro subform-macro [arg]
  `(do
     (inner-macro ~arg)))

Let's try to expand it:

user> (macroexpand-1 '(subform-macro "hello"))
(do (user/inner-macro "hello"))

user> (macroexpand '(subform-macro "hello"))
(do (user/inner-macro "hello"))

Since, (do ...) form is not a macro macroexpand-1 and macroexpand just return it and nothing more. Don't expect that macroexpand will do the following:

user> (macroexpand '(subform-macro "hello"))
(do (clojure.core/println "hello"))
like image 187
OlegTheCat Avatar answered Oct 07 '22 00:10

OlegTheCat


the difference is quite simple. First of all the background: when the compiler sees macro call it tries to expand it according to its definition. If the code, generated by this macro contains other macros, they are also expanded by compiler, and so on, until resulting code is totally macros-free. So macroexpand-1 just expands the topmost macro and shows the result (no matter does it generate another macro calls), while macroexpand tries to follow the compiler's pipeline (partially, not expanding macros in subforms. To make the complete expansion you should take a look at clojure.walk/maxroexpand-all).

small example:

user> (defmacro dummy [& body]
        `(-> ~@body))
#'user/dummy

this silly macro generates the call to another macro ( -> )

user> (macroexpand-1 '(dummy 1 (+ 1)))
(clojure.core/-> 1 (+ 1))

macroexpand-1 just expands dummy, but keeps -> unexpanded

user> (macroexpand '(dummy 1 (+ 1)))
(+ 1 1)

macroexpand expands dummy and then expands ->

like image 22
leetwinski Avatar answered Oct 06 '22 22:10

leetwinski