美文网首页
Compiling Clojure to Javascript

Compiling Clojure to Javascript

作者: onedam | 来源:发表于2020-05-23 10:03 被阅读0次

    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: [日本語]

    image

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

    Related posts:

    1. Compiling Clojure to JavaScript, pt. 2 – Why No Eval?
    2. Compiling Clojure to JavaScript, pt. 3 – The Himera Model
    3. A JavaScript Puzzler
    4. The ClojureScript Compilation Pipeline
    5. Why Clojure doesn’t need invokedynamic, but it might be nice

    相关文章

      网友评论

          本文标题:Compiling Clojure to Javascript

          本文链接:https://www.haomeiwen.com/subject/iplkahtx.html