ClojureScript Macro-Functions

December 12, 2015

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!

Tags: ClojureScript