read


read
or learn more

Compiling Clojure to Javascript

Jul 21, 2011

Compiling Clojure to Javascript pt. 1 of n

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!

Let’s not get crazy though

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.

Wholly Pragmatic

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.

Conclusion

So what is the advanced compilation output3 from Google’s Closure compiler for the last snippet?

2

Party indeed.

:F


  1. All caveats apply. The generated code samples are subject to change over time, but I hope the point is clear nonetheless. 

  2. Closure? Is there no better name for Google to have chosen? 

  3. 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. 

27 Comments, Comment or Ping

  1. Scarily cool stuff abound!

  2. Baishampayan Ghose

    Simply awesome! :-P

  3. John Palgut

    I love it. Absolutely love it.

  4. Felipe D.

    I guess you saved all the beef for a blog post! :D

    Good stuff.

  5. Tobias Raeder

    Fun times ahead :D i really like the choice of closure.

    Btw the rationale link for clojurescript should be https://github.com/clojure/clojurescript/wiki/Rationale instead of https://github.com/relevance/clojurescript/wiki/Rationale

  6. @Tobias

    Thanks for the correction.

  7. Hodge

    What about compiling javascript back to clojure? How much of a hassle do you think it would be to write such a compiler? Do you foresee any need for it?

  8. Ryan C.

    Beautiful. Thanks for writing this up!

  9. Hi fogus,

    How do you handle `this` when compiling Clojure functions to JavaScript? For example, consider a callback function given as argument to a method of a jQuery object. Suppose the callback needs to access `this`, which should be the jQuery object, not the global `this`.

    Maybe using some special syntax for representing methods?

    (I’m sorry if the question is not clear. Please, let me know if that is the case.)

  10. Well, it seems that some implicit markup screwed my text…

  11. @Hodge

    Making the round-trip back to ClojureScript is a non-objective. But if you want to tackle it then I would never discourage you. :-)

  12. One thing that troubles me here is the tight coupling between ClojureScript and Closure. Wouldn’t it be a better design decision to add a layer in the code that loosens this coupling and allows ClojureScript to target other JavaScript toolkits ?

  13. Anne Ominous

    Did you know that PeerBlock blocks your blog? I find that a bit weird, since you’re well known in open-source circles which is kind of the opposite from die-hard copyright maximalists and their organizations.

    Anyway, on the topic of ClojureScript, are there any plans to get eval and “native” macros working in it? It’s a bit funny that Javascript has eval (even if it works on strings rather than being homoiconic) but ClojureScript, which aspires to be a Lisp on top of Javascript, does not.

  14. @AnneOminous (I see what you did there;)

    I can’t say for sure why my site would be blocked by PeerBlock, but my understanding is that it works at the IP level. If my provider is blocked then so am I. It does make me want to investigate further, so thank you for the notification.

    As for eval it’s a good question. At the moment it is not on the radar. The main goal at the moment is to tighten the code generation while providing a more complete Clojure experience. Eval is not impossible, there are many Lisp/Scheme variants in the browser that pull it off. However, it’s just too heavy and complex. I suspect that if a real need arises then its exclusion will be reexamined. The bigger win IMO is the presence of the Reader. :-)

  15. There’s a fundamental problem for eval-ing any code that uses any other code (i.e. anything non-trivial) – you don’t know the minified names of anything, and, in advanced mode, the things you want to call might not even be present.

  16. @Yoav

    The coupling with Closure is not very tight at all. Its use in ClojureScript is nicely encapsulated (with plans for more abstraction) and its use in the core is an implementation detail. Likewise, the additional compilation phases it provides are optional. Keep in mind that the js that comes out of the ClojureScript compiler is legal and you can choose any post-processing tool that you wish to use on it.

  17. @Goulart

    The use and access of this is in design at the moment. Keep an eye on the associated ticket at http://dev.clojure.org/jira/browse/CLJS-26.

  18. Paul M Bauer

    @fogus @RichHickey I understand the architectural decisions that require the exclusion of eval. And it’s true that a dev environment in production just isn’t needed for 99% of prod use cases.

    But there is one development use case this precludes: live UI development. It’s common to rev on a live DOM with JS. No eval means ClosureScript UI devs can’t do that since there’s no DOM in the Rhino/ClojureScript/Clojure repl?

    Would a ClosureScript Compiler Service over AJAX/REST (server emitting non-minified JS, JS eval on the client) get part-way there for the dev case? (Access to vars from an evaled string would be problematic due to the way ClosureScript simulates lexical bindings.)

  19. Paul M Bauer

    P.S. That last sentence in parens should have been a question.

  20. Anne Ominous

    Lack of “eval” at runtime presents at least one significant problem, though, doesn’t it? You can’t get a REPL open on a running application to diagnose it, tweak settings, and possibly even fix buggy code on the fly without runtime “eval”.

  21. @fogus

    Based on what Rich Hickey said during the ClojureScript announcement and what I’ve seen in the code, ClojureScript generates JS code that is adjusted to the gClosure format, and uses goog here and there (please correct me if I’m wrong here). This cannot be used if my JS project is based on Dojo or YUI (these alongside with gClosure are the only libs that really support large scale web apps). I believe that a layer that separates gClosure from ClojureScript may be more then needed, and based on that layer add the binding to goog, Dojo and YUI.

  22. @Yoav

    You’re right about the separation and plans are in play to provide just that.

  23. Sergii

    What’s about debug?

    coffeescript has similar issue, but it compiles in quite good JS.

    But having code like you’ve shown, it’s much harder to debug and understand what goes wrong.

  24. @Sergii

    The advanced mode compilation is an optional step. You would debug the version that is not minified. Any other ClojureScript debugging tools have yet to be written, but they will.

  25. Patrick Logan

    A little bit late to comment here, but I am just trying clojurescript for the first time. I would like to use it with dojo (for the widgets, etc.) and clojurescript for the model. The interface between the two can be fairly minimal, something like dojo’s simple publish/subscribe API should be sufficient. Dojo does support some level of processing by the google-closure compiler. [1] I’m hoping this may work… if there are any updates to this topic…

    [1] http://dojotoolkit.org/reference-guide/build/#using-google-s-closure-compiler

  26. Patrick Logan

    for completeness, this…

    http://lukevanderhart.com/2011/09/30/using-javascript-and-clojurescript.html

    So it seems the narrow dojo pub/sub API is a good candidate for integration w/clojurescript, if you do not mind using dojo’s module/build system for those bits, and the clojurescript toolchain for the other bits.

    That will probably be acceptable in a significant “single-page app” that lives in the browser all day. Small mobile apps could be a different story, but that’s expected.

  27. I love this! ClojureScript is incredibly cool stuff.

Reply to “Compiling Clojure to Javascript”