Stefan Fehrenbach wrote a detailed summary of an attempt to use Google Closure Compiler on PureScript’s output:
His conclusion is that PS needs to be tweaked to annotate constructors as such, and that inconsistent use of record fields as r.f or r[“f”] confuses Closure and would need to be ironed out.
I agree with his conclusion,
I don’t think it would be too hard to either change PureScript itself, or write an alternative backend, to produce Closure-compatible code. I still think the easy way to better performance is through using Closure, rather than reimplementing optimizations in the PureScript compiler.
And it’s likely I will lean towards that.
Everything else I’ve tried is woefully insufficient. Zephyr did nothing to my code, just made it smaller by about 1kb. Closure and UglifyJS can only do some name mangling and removing whitespace at this point, which a gzip compressor is better at. The key things to remove are dead code (which purs bundle is fine at), and removing curried functions. Phil is not interested in adding uncurrying to the compiler, so I’ll have to add it to a fork. This is a step that Closure will not do (because it’s not normal in JS), but is a huge saving in size and performance, and removing it permits Closure to do better work.
I’ve done this before on Fay, which was pretty much the same as PS. Curried, with also a thunk forcer for laziness. Give Closure the right hints and it can inline things away.
Watch this:
(function(){
var Show_string = { show: function(s){ return s; } };
function print(d,v){
console.log(d.show(v));
}
function greet(d,v){
return print(d,v + "!");
}
return greet(Show_string, "Mary");
})();
Here’s what Google Closure outputs:
console.log("Mary!");
Type class instance dictionaries removed!
However, stick currying in,
(function(){
var Show_string = { show: function(s){ return s; } };
function print(d) {
return function(v){
console.log(d.show(v));
}
}
function greet(d){
return function(v){
return print(d)(v + "!");
}
}
return greet(Show_string)("Mary");
})();
and here’s what you get:
(function() {
function c(a) {
return function(b) {
console.log(a.show(b));
};
}
return function(a) {
return function(b) {
return c(a)(b + "!");
};
}({show:function(a) {
return a;
}})("Mary");
})();
In other words, currying blocks Closure from doing its job. This is the one thing Closure is bad at. However, it is excellent and stable at everything else.
All we have to do is walk the tree and find:
- Definitions of functions with chains of
function(x){ return function(y) { ...
and call that “arity_2”.
- Check whether this function is saturated (called) with 2 arguments anywhere. If so, spit out the arity_2 version of it.
(function(){
var Show_string = { show: function(s){ return s; } };
function print(d) {
return function(v){
console.log(d.show(v));
}
}
function greet(d){
return function(v){
return print(d)(v + "!");
}
}
function print_2(d,v){
console.log(d.show(v));
}
function greet_2(d,v){
return print_2(d,v + "!");
}
return greet_2(Show_string, "Mary");
})();
Then closure produces the console.log("Mary!");
that we desire. It drops the unused unsaturated curried versions.
Writing up because I don’t have time to do this now, but may have time to do it later. I’ll need this comment to warm up my cache next time.