ClojureScript Macro Sugar
Update March 21, 2018: Content from this post has been incorporated into the ClojureScript site Guide: ns Forms.
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.
Primitives
For the sake of an example, let's assume that we have the following in src/foo/core.clj
:
(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)
Now, :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:require
/:refer
and:use
/:only
are essentially dual forms of each other. So, thens
form 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 foo
:
(ns bar.core
(:require-macros [foo.core :as foo]))
(foo/add 2 3)
Sugar
The examples above are all making use of the :require-macros
primitive.
Frequently, though, you will be consuming code that comes from a library that offers both runtime code (functions and other def
s) 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 ns
-form sugar, :refer-macros
that lets you write instead:
(ns bar.core
(:require
[foo.core :refer [subtract]
:refer-macros [add]]))
(add 2 3)
(subtract 7 4)
The :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)
Implicit Sugar
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)
Implicit Refer
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!
Docs
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 docstring. In a pinch, (doc ns)
is your friend.
Desugaring in the REPL
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 ns
form.
The require and require-macros REPL Specials
You can use require
and 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.
Update June 17, 2017: With ClojureScript 1.9.293, require
and require-macros
are no longer REPL specials, but macros. Read more at ClojureScript require
outside ns
.
Summary
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.