Skip to content

Instantly share code, notes, and snippets.

@mfikes
Last active January 5, 2020 22:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfikes/40b634b1ef8e317d8d5a to your computer and use it in GitHub Desktop.
Save mfikes/40b634b1ef8e317d8d5a to your computer and use it in GitHub Desktop.
Implicit Macro Loading

ClojureScript macros can be loaded using the :require-macros primitive in ns forms (or require-macros REPL special).

But, there is a shortcut you can take if a runtime namespace requires macros from its macro namepace file. This might be a bit tricky to understand so here is a concrete example.

Let's say you have a macro file:

$ cat src/foo/core.clj
(ns foo.core)

(defmacro add
  [a b]
  `(+ ~a ~b))

and a runtime file:

$ cat src/foo/core.cljs
(ns foo.core)

(defn subtract
  [a b]
  (- a b))

Let's start the REPL and try using these defs:

$ java -cp cljs.jar:src clojure.main -m cljs.repl.node
ClojureScript Node.js REPL server listening on 52109
To quit, type: :cljs/quit
cljs.user=> (require '[foo.core :as foo])
nil
cljs.user=> (foo/subtract 7 3)
4
cljs.user=> (foo/add 2 3)
WARNING: Use of undeclared Var foo.core/add at line 1 <cljs repl>
TypeError: Cannot read property 'call' of undefined
    at repl:1:101
    at repl:9:3
    at repl:14:4
    at Object.exports.runInThisContext (vm.js:54:17)
    at Domain.<anonymous> ([stdin]:41:34)
    at Domain.run (domain.js:191:14)
    at Socket.<anonymous> ([stdin]:40:25)
    at emitOne (events.js:77:13)
    at Socket.emit (events.js:169:7)
    at readableAddChunk (_stream_readable.js:146:16)
cljs.user=> (require-macros '[foo.core :as foo])
nil
cljs.user=> (foo/add 2 3)
5

As you can see, as expected, you can't make use of the add macros without explcitly making use of the require-macros REPL special.

Now, lets make a minor change to the runtime file to require the macros file:

$ cat src/foo/core.cljs 
(ns foo.core
  (:require-macros foo.core))

(defn subtract
  [a b]
  (- a b))

The only difference is the addition of the :require-macros spec in the ns form.

Now, you can do this:

$ java -cp cljs.jar:src clojure.main -m cljs.repl.node
ClojureScript Node.js REPL server listening on 58951
To quit, type: :cljs/quit
cljs.user=> (require '[foo.core :as foo])
nil
cljs.user=> (foo/subtract 7 3)
4
cljs.user=> (foo/add 2 3)
5

As you can see, the macros are loaded and additionally made avaialble via the foo alias.

If you do (doc ns) you will see this described as Implicit macro loading.

Hope this helps!

If you are curious as to how this works, cljs.analyzer/macro-autoload-ns? is invoked by the analyzer in cljs.analyzer/desugar-ns-specs when processing ns forms. (Using desugar-ns-specs in bootstrapped environments was illustrated here.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment