this is the first entry in an n-part series explaining the techniques and design principles of ClojureScript. translations: [日本語]
Over the past couple of months I’ve been working with the Clojure/core and friends to develop ClojureScript – a new Clojure compiler targeting JavaScript. This post is about a couple of the approaches that we’ve taken and the practical use of the Google CloSure compiler.
Given the arity overloaded function below:
(fn
([t] t)
([x y] y)
([a b & zs] b))
The ClojureScript compiler currently compiles it into something1 like this:
(function() {
var foo = null;
var foo__5675 = function(t) {
return t
};
var foo__5676 = function(x, y) {
return y
};
var foo__5677 = function(a, b, zs) {
zs = Array.prototype.slice.call(arguments, 2);
return b
};
foo = function(a, b, zs) {
switch(arguments.length) {
case 1:
return foo__5675.call(this, a);
case 2:
return foo__5676.call(this, a, b);
default:
return foo__5677.apply(this, arguments)
}
throw"Invalid arity: " + arguments.length;
};
return foo
})();
This is perfectly legal Javascript code, and as a nice bonus is
fairly performant. However, the ClojureScript compiler
cljsc provides an optional
pass that compiles the generated JavaScript to JavaScript using the
Google
Closure compiler.2 The
code that comes out of this second pass is really pretty cool:
(function() {
var d = null, c = function(b, a) {
Array.prototype.slice.call(arguments, 2);
return a
};
return function(b, a) {
switch(arguments.length) {
case 1:
return b;
case 2:
return a;
default:
return c.apply(this, arguments)
}
throw "Invalid arity: " + arguments.length;
}
})();
Notice that the two helper functions foo__5676 and
foo__5677 are now inlined. Google’s
Closure compiler is a true optimizing compiler
providing powerful dead-code elimination. In fact, on its highest
optimization setting, the Closure compiler actually
generates the following for the definition above:
// intentionally blank
Why? Because the original function is never actually called. In the words of Rich Hickey:
Party!
The Closure compiler is very powerful, but some
choices in the way that Clojure compiles were chosen so
as not to rely too heavily on its optimizations. For example, we could
use a common trick for compiling let blocks
(let is a structure that provides lexical bindings),
namely, to convert:
(let [a 1 b 2 a b] a)
Into:
(function() {
return (function foo__2313 (a) {
return (function foo__2314 (b) {
return (function foo__2315 (a) {
return a;
}(b));
}(2));
}(1));
})();
and let Google’s Closure compiler transform that into something like the following:
(function() {
var a = 1;
var b = 2;
return b;
})();
But instead, Clojure’s compiler uses renaming to simulate lexical bindings and generates something like the following:
(function (){
var a__847 = 1;
var b__848 = 2;
var a__849 = b__848;
return a__849;
})();
Where Clojure’s compiler can optimize without performing whole-program analysis and dead-code elimination it will.
Clojure is doggedly pragmatic in the way that it defers to the JIT for runtime performance and also in its superior Java interop. ClojureScript is likewise pragmatic in that it leverages the Google Closure tools for its implementation and minification strategy. For any platform that Clojure targets now and in the future, the question will always be asked: where are the libraries? There were many potential choices for ClojureScript, but Closure, while not perfect, was the best match given the motivating forces behind ClojureScript.
So what is the advanced compilation output3 from Google’s Closure compiler for the last snippet?
2
Party indeed.
:F
All caveats apply. The generated code samples are subject to change over time, but I hope the point is clear nonetheless.↩︎
Closure? Is there no better name for Google to have chosen?↩︎
Actually, in a statement context, of which I will talk
about in a later post, the code (let [a 1 b 2 a b] a) again
compiles to //nothingness, but I hope the point is clear
nonetheless.↩︎