ClojureScript Macro Sugar
In ClojureScript, macros are handled a bit differently than in Clojure. In particular, the
ns form supports
:require-macros, along with some simplifying sugar and implicit loading behavior that this post will cover.
For the sake of an example, let's assume that we have the following in
(ns foo.core) (defmacro add [a b] `(+ ~a ~b))
To use this macro in some ClojureScript source, you can employ the
:require-macros spec as
(ns bar.core (:require-macros foo.core)) (foo.core/add 2 3)
:require-macros is designed to work a lot like
:require. So, for example, you could directly refer the
add symbol into your namespace:
(ns bar.core (:require-macros [foo.core :refer [add]])) (add 2 3)
An alternative to the above (which is infrequently seen, but worth covering for completeness), is
:use-macros. In ClojureScript
:onlyare essentially dual forms of each other. So, the
nsform in the above could have just as well been written
(ns bar.core (:use-macros [foo.core :only [add]])).
You could also set up a namespace alias
(ns bar.core (:require-macros [foo.core :as foo])) (foo/add 2 3)
The examples above are all making use of the
Frequently, though, you will be consuming code that comes from a library that offers both runtime code (functions and other
defs) and macros, all from the same namespace.
So, continuing on our example, let's say that there is a
src/foo/core.cljs file with
(ns foo.core) (defn subtract [a b] (- a b))
Now if you wanted to use both add and subtract, you might do something like this:
(ns bar.core (:require-macros [foo.core :refer [add]]) (:require [foo.core :refer [subtract]])) (add 2 3) (subtract 7 4)
But, there is a bit of
:refer-macros that lets you write instead:
(ns bar.core (:require [foo.core :refer [subtract] :refer-macros [add]])) (add 2 3) (subtract 7 4)
:refer-macros above really is just sugar, and it is algorithmically desugared into the previous form by the compiler.
Similarly, there is
:include-macros sugar that you can use to signal that the macros namespace should be required using the same specifications as the runtime namespace. So for example, this works:
(ns bar.core (:require [foo.core :as foo :include-macros true])) (foo/add 2 3) (foo/subtract 7 4)
The above desugars into the more repetitively verbose primitive form:
(ns bar.core (:require-macros [foo.core :as foo]) (:require [foo.core :as foo])) (foo/add 2 3) (foo/subtract 7 4)
In cases where a runtime namespace being required internally requires its own macro namespace (meaning a namespace with the same name), then you implicitly get the
:include-macros sugar for free.
To illustrate this, let's say our
src/foo/core.cljs file instead looked like this:
(ns foo.core (:require-macros foo.core)) (defn subtract [a b] (- a b))
Now, you can consume things like this:
(ns bar.core (:require [foo.core :as foo])) (foo/add 2 3) (foo/subtract 7 4)
What about this nice-looking simplification?
(ns bar.core (:require [foo.core :refer [add subtract])) (add 2 3) (subtract 7 4)
This won't work today, but it is the subject of a potential future enhancement. This would be nice sugar because it essentially smoothes over one remaining difference between ClojureScript and how macros can be consumed in Clojure.
Update July 3, 2016: Changes have landed for this feature!
If you are ever at a REPL and need a quick reference to the above topics, the docstring for the
ns special form has been updated. The sugared forms are referred to as inline macro specification and the implicit sugar is referred to as implicit macro loading. A fairly comprehensive example of desugaring is included in the docstrring. In a pinch,
(doc ns) is your friend.
The compiler code that performs desugaring is not available via a stable public API. But you can still call it nonetheless. This is especially easy if you are in a bootstrapped REPL. Just do the following:
(require '[cljs.analyzer :as ana]) (defn desugar [[_ ns & specs]] `(ns ~ns ~@(ana/desugar-ns-specs specs)))
With this in place, you can evaluate things like
(desugar '(ns bar.core (:require [foo.core :refer [subtract] :refer-macros [add]])))
and get back
(ns bar.core (:require (foo.core :refer [subtract])) (:require-macros (foo.core :refer [add])))
I find being able to do this helps if there is an interesting corner case, and I really want to see what the compiler is doing with my
You can use
require-macros to dynamically load code into your REPL. What's interesting is that the desugaring described in this post also works for these REPL specials.
This is an implementation detail, but it helps you see how this is accomplished: When you issue
(require-macros '[foo.core :as foo :refer [add]])
at the REPL, this is internally converted into an
ns form that looks like
(ns cljs.user (:require-macros [foo.core :as foo :refer [add]]))
And, importantly, when you use
require, a similar
ns form is employed, and it is subject to all the desugaring described above.
Hopefully these detailed examples help clarify how desugaring works. The overall intent of sugaring is to simplify ClojureScript macro usage, but sometimes unpacking how these extra capabilities work leads to a better understanding for those times when you either want or need to know what is really going on.