Why can't I write macros in Clojurescript?

macros
clojurescript

#1

In 2019, after hearing Clojurescript touted without much complaint, I have been told that I can’t write macros in Clojurescript (which, as far as I can tell, seems to be true). Why can’t I, and why is this still the case?


#2

Yes, I see https://clojurescript.org/about/differences#_macros . Still hard to believe, though.


#3

Because macros run during the “compile” phase, and the ClojureScript compiler runs on the JVM (and assumes that Google Closure is available for code compression and tree-shaking).


#4

I’ve never really thought about how inferior the client-side (browser-based) hosting experience is compared to JVM (and, once upon a time, lisp machines). Interesting point of reflection.


#5

There may not be a technical blocker that precludes macros in ClojureScript. (For example, see https://github.com/mfikes/chivorcam — even with that, you can see there are ways the unaddressed issues could be solved.)

IMHO it is more that what we have works, and while we might gain by trying to change things, there isn’t sufficient perceived value in pursuing that.


#6

Out of curiosity; what do you want to do with macros after compilation that you can’t do now? To me it seems like the main value of macros are to be able to generate source code before runtime. :slight_smile:


#7

While you can live with it if you are in a JS-only environment, using the same codebase in both places is a bit of a PITA. Especially because it’s not an interop thing, but a core Clojure concept.


#8

In my case, I was trying to add an “admins-only” wrapper around a set of forms on the front-end, which could include a get-data request as well as the actual data structure for rendering. Seemed like a perfect place for a macro.


#9

Aren’t those functions defined before compilation? There are no issues with using macros in cljs, only writing them. :slight_smile: Sounds to me like a macro you could write in clj, and include using :require-macros?


#10

Yeah, you’re right. I’ve unwittingly done this before with cljc files. Yesterday was the first time I’d innocently attempted to write a macro in cljs and spent annoyingly long debugging before I learned, “Oh, you can’t do that.”


#11

Haha, yeah, I’ve run into the same issue. It’s not always entirely clear wether you’re in jvm-land or js-land.


#12

In the slack when this was discussed yesterday, David Nolen made a good point though.

If you wanted ClojureScript macros, you need to bundle the whole compiler with your JS app. Which would considerably increase your page load times.


#13

That is a very good point. What does it mean that we are aware/thinking about these as Clojure developers? By necessity, I guess it indicates a more-than-average awareness of your compiler and of compile-time vs just-in-time. I never had to think about those things before with C, C++, Perl, Java, etc. I don’t think that having a heightened awareness of that is necessarily bad, though; it is the price we pay for having a macro system and having hosted languages, and perhaps even for having fully-powered REPLs.


#14

I guess, if you use macros, you should know when they run? Clojure is hosted, and we pay the price of awareness of the platform instead of being isolated from it.

You can still do code generation and eval it, though. And that does add to your artifact size.


#15

I actually like to think of Clojure/ClojureScript as the most powerful Java/JS library ever.

Like at the core your either doing Java or JS development, but you imported a powerful Turing complete framework to make that easier, and that’s Clojure[Script].


#16

That’s often my elevetor pitch of Clojure for sceptics. :smiley: “It’s a library for examining and modifying a compiled program while it’s running. Makes for super fast debugging.” Then I might later go on to talk about more general benefits, but the libraryness of Clojure is often a good thing when you talk to someone deep in the host platform.


#17

IMO, ClojureScript should support macro directly


#18

I think there is some confusion in this thread. I regularly use ClojureScript macros without the slightest issue.

What you have to know and remember is that ClojureScript macros are actually processed by Clojure, during compile time, which means they can’t be in your .cljs files. Apart from some minor annoyances and confusing documentation (there were a number of changes to the ns form syntax for including macros, and to this day I do not know what the proper usage is), macros work just fine.


#19

Let’s clear this up. Please correct this if I’ve made an error.

If ClojureScript had proper self-host macros, dead code elimination wouldn’t work and you’d have to ship the compiler and standard library over the wire along with the entire classpath. This is why ClojureScript does not supply cljs.core/eval, but rather cljs.js/eval and cljs.js/compile-str

As it stands today, with ClojureScript macros constrained to compile time you can write portable CLJC macros, as long as you hold in your head that they will run in the JVM at compile time.

(ns contrib.template
  #?(:cljs (:require-macros [contrib.template])
     :clj (:require [clojure.java.io :as io])))

; Macro to inline a text resource on compile classpath as clojure literal
#?(:clj
   (defmacro load-resource [filename]
     (-> (io/resource filename) slurp)))


#20

Putting aside Clojurescript for the moment and thinking only of JVM-based Clojure, if I create an uberjar, is the compiler included in it? If I include macros? If I use eval?