Collapsing Macro Tower

In a previous post, I had written about a consequence of the ClojureScript macro compilation model, and how it requires a tower of macro namespaces when self-hosted.

Well, in the case that a macro expands into a macro call, the staging rules are satisfied and no tower is needed.









Here's more detail on the subject. The macros in the previous post satisfy this constraint and the bar.core namespace can be more simply written:

(ns bar.core) (defmacro unless [pred a b] `(if (not ~pred) ~a ~b)) (defmacro abs [x] `(unless (neg? ~x) ~x (- ~x)))

Note that, for this code to work without qualifying the unless symbol, you need tools.reader 1.0.0-alpha3 or later.

On the surface, it looks like abs is calling unless within the same compilation stage. But in reality, abs only later expands into a call to unless . For example:

(abs -3)

expands to

(unless (neg? -3) -3 (- -3))

which expands to

(if (not (neg? -3)) -3 (- -3))

(You can see the difference for yourself if you mess around with macroexpand-1 and macroexpand in a REPL.)

The above differs from another pattern often employed in macros, where macro code is invoked during expansion. An example might be a macro that computes the square root of a numeric literal at compile time, checking for a negative value:

(ns baz.core) (defmacro unless [pred a b] `(if (not ~pred) ~a ~b)) (defmacro sqrt [x] (unless (neg? x) (Math/sqrt x) (throw (ex-info "Real plz" {}))))

In this case, sqrt is violating the staging rules by consuming the unless macro from within the same compilation stage, and a tower would be needed to fix it. The same thing would occur if you have a function in a macro namespace that calls upon a macro defined in that stage.

Nevertheless, the collapsing variant is very nice because macros often dictate whether a library is directly usable in bootstrapped ClojureScript, or, how much porting is required. This capability should make it easier to consume existing ClojureScript libraries in bootstrapped environments.