I have a project that targets both Clojure (JVM) and ClojureScript via CLJX.
I have a macro that takes a thunk and creates an IDeref
instance to execute that thunk every time it is dereferenced (with deref
or @
).
Since it's a macro, it has to go in a .clj file. The problem is that the IDeref
interface is different for Clojure and ClojureScript. In Clojure I need to generate this:
(reify clojure.lang.IDeref
(deref [_] thunk))
In ClojureScript I need to generate this:
(reify IDeref
(-deref [_] thunk))
Since this a macro I can't use the featuer-expression-like syntax from cljx (e.g. #+cljs -deref
) to reconcile the code for my two target platforms. Here's what I ended up doing:
(defmacro make-thunk [exp]
`(reify ~params/IDeref-interface
(~params/IDeref-method [_#] ~exp)))
I then have a separate params.clj in both the clj and cljs source trees, each of which has a def
for each needed symbol.
This works, but it's really ugly, and it feels like a dirty hack.
I'd really like to keep all of my macros in the same namespace. I'd rather not have to define every platform-dependent symbol for my macros in a separate file. I already have platform-dependent compat.clj and compat.cljs files in the two source trees. Having to add more files to support platform-dependent macros is starting to make things feel cluttered.
Is there a cleaner solution to this problem?
Inside the body of a macro, (:ns &env)
will be truthy when expanding in ClojureScript but not in Clojure.
This is currently the "best practice" for writing platform-specific macros:
(defmacro platform []
(if (:ns &env)
:CLJS
:CLJ))