dosync Archive Pages Categories Tags

ClojureScript Next

29 July 2015

I've been enamoured with languages in the Lisp family ever since I first encountered The Structure and Interpretation of Computer Programs now more than a decade ago. At the time I was disappointed that such beautiful systems found so little use in day to day programming. Lisp seemed both deeply pragmatic (Objects!) and deeply sophisticated (Meta-circular Interpreters!). But Java, bless its heart, was the language of the day, and despite Steele's characterization, it seemed miles away from Scheme or anything like it.

For many years it seemed unlikely that Lisp would be more widely used by the working programmer. So it's with a constant sense of wonder that I look around at a rapidly expanding ClojureScript community that shares the same love for simpler yet more expressive systems. It seems fair to attribute this quickening pace of adoption to ClojureScript's steadfast dedication to pragmatism.

But always taking the practical route can cut off unforeseen avenues and vistas.

Today ClojureScript embraces the less practical and "dream bigger" side of Lisp.

ClojureScript can compile itself.

Yeah.

There's a lot to talk about, but first a few words on another big change.

Clojurescript 1.7

ClojureScript now has a version number. Enthusiastic users have often asked How long till 1.0?. However 1.0 would not correctly reflect the time, effort, and feature set that comes with four years of very active development. Instead we're adopting 1.7 as this communicates the incredibly important relationship that ClojureScript has with its parent language, Clojure.

This close relationship means the differences between Clojure and ClojureScript are largely uninteresting. So much so that with the help of reader conditionals and some dedicated collaborative effort, self compilation came rather quickly.

Enough ado, let's get to it.

Say Hello To Our Old Friend, Eval

The following is a simple ClojureScript program that creates a definition and immediately invokes it. Click the EVAL button.

The humble result hides the enormity of the event :) If you know ClojureScript (or even if you don't), feel free to modify the source and evaluate whatever you like.

So what is happening here?

We grabbed a string out of CodeMirror, read it via tools.reader into persistent data structures, passed it into the ClojureScript analyzer, constructed an immutable AST, passed that AST to the compiler, and generated JavaScript source with inline source maps and eval'ed the result.

All of this happened inside of your web browser.

To be very clear, this is precisely the same ClojureScript compiler that runs on the Java Virtual Machine running on Plain Old JavaScript.

For the unbelievers, open Chrome DevTools, open the Sources tab and you should see something like the following:

Let's dig into details that enabled the humble result above.

Reading

The first step is converting a string into a series of data structures. What other languages call parsing is traditionally called reading in Lisp. It's important for this process to be fast. We've spent years tuning ClojureScript code generation for modern JavaScript JITs without catering to any specific engine (for a long time V8 was king, but these days JavaScriptCore is screaming ahead of the pack).

So how long does it take to read the entire standard library (about 10,000 lines of code) into persistent data structures? Click the following button. This will download the entire standard library and then measure the time it takes to read all of it. Make sure to click a few times and you will observe it get faster.

On my 3.5ghz iMac this takes ~60ms under WebKit Nightly, ~80ms under Chrome Canary, and ~60ms under Firefox Nightly. These numbers are only 1.5X to 2X slower than ClojureScript JVM performance.

A big shoutout to Andrew Mcveigh and Nicola Mometto for pushing the ClojureScript port of tools.reader through.

Now let's consider the next step, analysis.

Analysis

After successfully reading a form, that form is passed to the analyzer. This step produces an AST. ClojureScript's AST is composed entirely of simple immutable values. Similar to the strategy taken by many popular JavaScript parsers and compilers, a data oriented representation means the AST can be manipulated easily without an API of any kind.

A big shoutout to Shaun LeBron and Jonathan Boston's work on porting clojure.pprint to ClojureScript so that we can see a pretty-printed AST.

From the AST we can now generate JavaScript.

Compilation

The compiler generates JavaScript directly from the simple data oriented AST:

Let's show a less trivial example. The following includes a library import. Users of bootstrapped ClojureScript have total control how library names are resolved to actual sources. In this case we've configured libraries to be fetched via a simple XMLHttpRequest. In order to expand the macros we must first get the macros file, compile it, eval it, and then continue to parse, analyze, and compile the foo.core namespace.

This example demonstrates not only how pluggable the bootstrapped compiler is, but how anything compilable by ClojureScript JVM is compilable by ClojureScript JS.

Conclusion

There's little doubt that this feature enhancement will create an avalanche of new innovation. You can already run ClojureScript on your iPhone with Mike Fikes' Replete, build iOS apps with React Native, and write fast starting shell scripts with Planck or with Node.js. Suffice to say we've only scratched the surface of an iceberg of potential.

To be clear, this is not something you want to use for the traditional ClojureScript use case - building typical web applications. The output file starting size is simply too large (~300K gzipped). But there are now many new reachable targets where this cost is not a constraint as in the examples previously enumerated.

After something this significant you think we would be done, but there's a lot more good stuff coming. Maria Geller's excellent JavaScript module work should make it a breeze to integrate the various module types you find in the wild including the new ES 2015 standard. Further out we're looking into automatically generating externs where possible.

You have an amazing tool for thought at your fingertips. You can play with the latest goodies today. As I said at EuroClojure:

Keep calm and try to take over the world.

Happy hacking!