ClojureScript Macro-Functions
In ClojureScript—unlike Clojure—a macro and a function can have the same name.
Here is an example. Lets say you have
src/foo/core.clj
:
(ns foo.core)
(defmacro add [a b]
`(+ ~a ~b))
and
src/foo/core.cljs
:
(ns foo.core)
(defn add [a b]
(+ a b 0.001))
In the function definition above, I added an additional 0.001
to make it easier to see whether the macro or function is being invoked in different contexts. Let's try using them:
$ java -cp cljs.jar:src clojure.main -m cljs.repl.nashorn
To quit, type: :cljs/quit
cljs.user=> (require-macros 'foo.core)
nil
cljs.user=> (require 'foo.core)
nil
cljs.user=> (foo.core/add 1 2)
3
cljs.user=> (apply foo.core/add [1 2])
3.001
cljs.user=>
Preference will be given to the macro over the function in the contexts where both are applicable. In the example using apply
, only the function can be used.
What is this capability good for?
The main one is performance. A macro can be used to achieve inlining, so something like cljs.core/+
exists as a macro so that addition can be inlined, ultimately resulting in the JavaScript +
operator in the generated code. But it also exists as a function so it can be used in higher order contexts like (reduce + (range 101))
.
In fact, if you study the implementation of cljs.core/+
, you'll see that the function—for the 2-argument variant—is essentially straightforward and invokes the macro. The macro, on the other hand expands to code like (js* "(~{} + ~{})" 1 2)
which emits the JavaScript ((1) + (2))
. In other words, some careful code is being crafted to achieve inlined native host arithmetic when possible, and Lispy higher-order goodness when not.
The doc
and source
functions display content from the function—this behavior is consistent with the notion that the macro is akin to a performance-related implementation detail.
What else is this capability good for? I suppose it makes it easy to introduce a macro in addition to a function without the need to resort to techniques like giving the function a special name (ending in a *
for example). But, beyond that, I have no extensive real-world experience with using this capability, so I've unfortunately got nothing in terms of "best practices" advice.
Perhaps this is an advanced capability that, like type-hinting, is appropriate in the standard library, where performance is critical, but which only rarely finds its way in to application code.
I suppose, in the end, sometimes you need a cow, but a dog can be faster. With ClojureScript you can have both!