3

Problem Description

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.

My Question

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?

DaoWen
  • 32,589
  • 6
  • 74
  • 101

2 Answers2

3

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))
  • Do you have any links to back up your claim about "best practice"? This is really convenient now, but it seems like the JVM Clojure implementation could suddenly decide to add a `:ns` to `&env` and then everything would be broken. In contrast, a conflicting `cljs.analyzer/*cljs-ns*` shouldn't pop up since that namespace is already claimed by a well-known project. – DaoWen Feb 27 '15 at 23:48
  • 1
    Perhaps "best practice" was not the correct term, but my answer is the [preferred method of Chas Emerick, the current maintainer of cljx](https://groups.google.com/forum/#!msg/clojurescript/iBY5HaQda4A/w1lAQi9_AwsJ). It is also the method that most popular cljx libraries (like [Schema](https://github.com/Prismatic/schema/blob/cd969f241f79e16da5b1a34ed4678e028b5c05a0/src/clj/schema/macros.clj#L12-21)) use. – Dom Kiva-Meyer Feb 28 '15 at 04:41
2

This should do the trick:

(defn compiling-cljs?
  "Returns true if we seem to be compiling ClojureScript, false otherwise.

  This is a Clojure function, meant to be called by macros targeting both
  Clojure and ClojureScript."
  []
  (if-let [cljs-ns-var (resolve 'cljs.analyzer/*cljs-ns*)]
    (some? @cljs-ns-var)
    false))

Use like so:

(defmacro foo []
  (if (compiling-cljs?)
    1
    2))

(I have a feeling this problem has been solved before, but can't seem to find a reference.)

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212