Portable Macro Musing

June 19, 2015

With the work currently underway for bootstrapped ClojureScript, it appears that macros will be part of the game, so long as you abide the distinction between compile time and runtime and keep em separated.

Very cool! If we are careful, can we write completely portable libraries and application code?




A small bit of background: for the current ClojureScript compiler (let's call it ClojureScript JVM), macros are defined in Clojure. It has been typical to put your macros in *.clj files and everything else in *.cljs files.

With the bootstrapped ClojureScript compiler (let's call it ClojureScript JS), custom macros will perforce need to be written in ClojureScript, but still kept separate as is required by ClojureScript JVM.

This got me thinking that reader conditionals could be used to write macro source files that are portable between ClojureScript JVM and ClojureScript JS. But, I wanted to go farther with the gedankenexperiment to extend this portability to Clojure as well. (Can we write code that works in all three environments?)

So, taking the nice str->int operation defined in Daniel Compton's fine Clojure Reader Conditionals by Example, here is a portability stress test involving both compile-time and runtime host interop, using reader conditionals for both. The code below adds two numbers, both which come from strings, where one is known at compile time and the other is known at runtime:

Compile time (src/foo/macros.cljc):

(ns foo.macros)

(defmacro str->int [s]
  #?(:clj  (Integer/parseInt s)
     :cljs (js/parseInt s)))

Runtime (src/foo/core.cljc):

(ns foo.core
  (#?(:clj  :require 
      :cljs :require-macros)
    [foo.macros]))

(defn str->int [s]
  #?(:clj  (Integer/parseInt s)
     :cljs (js/parseInt s)))

(defn add-five [s]
  (+ (str->int s) 
     (foo.macros/str->int "5")))

These two .cljc source files work in both Clojure and ClojureScript JVM:

Clojure:

user=> (require 'foo.core)
nil
user=> (foo.core/add-five "3")
8

ClojureScript JVM:

cljs.user=> (require 'foo.core)
nil
cljs.user=> (foo.core/add-five "3")
8

In the future, ClojureScript JS would make use of the :cljs branch in the macro.

The big question in my mind: Will these two source files work unchanged with ClojureScript JS? Only time will tell!

Update July 23, 2015—Yes, ClojureScript JS will indeed work with the files unchanged!

Tags: Clojure ClojureScript Bootstrap