-
-
Save bobzhang/9f27a5a0bd730e8d3503bf5d058a58a7 to your computer and use it in GitHub Desktop.
A list of languages which compile to JS (Elm, Purescript, OCaml) | |
(Inspired by this thread: https://groups.google.com/forum/#!topic/elm-discuss/Um7WIBTq9xU) | |
They all support curry calling convention by default. | |
Some interesting results: | |
1. `min` is curried function, only OCaml(BuckleScript) managed to optimize this overhead. | |
2. All optimize the self tail call | |
3. Only BuckleScript and PureScript type-specialized comparison functoin (>=) and inlined | |
4. PureScript generate a raise function which does not make sense | |
5. Only BuckleScript and PureScript generates module by module | |
Try websites: | |
http://elm-lang.org/try | |
http://try.purescript.org/ | |
http://bloomberg.github.io/bucklescript/js-demo/ | |
Only BuckleScript work offline (the compiler is compiled into JS too) |
range : Int | |
range = 1000000000 | |
testProg : Int -> Int | |
testProg n = -- do some work | |
let lmt = min (n + 100000000) range in | |
let loop i = | |
if i >= lmt then i else | |
loop (i + 1) in loop n |
var _user$project$Temp1482759649866537$range = 1000000000; | |
var _user$project$Temp1482759649866537$testProg = function (n) { | |
var lmt = A2(_elm_lang$core$Basics$min, n + 10000000000, _user$project$Temp1482759649866537$range); | |
var loop = function (i) { | |
loop: | |
while (true) { | |
if (_elm_lang$core$Native_Utils.cmp(i, lmt) > -1) { | |
return i; | |
} else { | |
var _v0 = i + 1; | |
i = _v0; | |
continue loop; | |
} | |
} | |
}; | |
return loop(n); | |
}; |
let range = 1000000000 | |
let testProg n = | |
let lmt = Pervasives.min (n + 100000000) range | |
in | |
let rec loop i = | |
if i >= lmt | |
then i | |
else loop (i + 1) | |
in | |
loop n |
// Generated by BUCKLESCRIPT VERSION 1.2.1 , PLEASE EDIT WITH CARE | |
'use strict'; | |
var Pervasives = require("stdlib/pervasives"); | |
function testProg(n) { | |
var lmt = Pervasives.min(n + 100000000 | 0, 1000000000); | |
var _i = n; | |
while(true) { | |
var i = _i; | |
if (i >= lmt) { | |
return i; | |
} | |
else { | |
_i = i + 1 | 0; | |
continue ; | |
} | |
}; | |
} | |
var range = 1000000000; | |
exports.range = range; | |
exports.testProg = testProg; |
module Main where | |
import Prelude | |
range = 1000000000 | |
testProg n = -- do some work | |
let lmt = min (n + 100000000) range | |
in | |
let loop i = | |
if i >= lmt | |
then i | |
else loop (i + 1) | |
in | |
loop n |
"use strict"; | |
var Prelude = require("../Prelude"); | |
var Data_Ord = require("../Data.Ord"); | |
var Data_Semiring = require("../Data.Semiring"); | |
var range = 1000000000; | |
var testProg = function (n) { | |
var lmt = Data_Ord.min(Data_Ord.ordInt)(n + 100000000 | 0)(range); | |
var loop = function (__copy_i) { | |
var i = __copy_i; | |
tco: while (true) { | |
var $0 = i >= lmt; | |
if ($0) { | |
return i; | |
}; | |
if (!$0) { | |
var __tco_i = i + 1 | 0; | |
i = __tco_i; | |
continue tco; | |
}; | |
throw new Error("Failed pattern match at Main line 9, column 8 - line 11, column 26: " + [ $0.constructor.name ]); | |
}; | |
}; | |
return loop(n); | |
}; | |
module.exports = { | |
range: range, | |
testProg: testProg | |
}; |
@raysegantii cool, just make sure is it the output of clojurescript (not through google closure)? Since the output of all other compilers does not go another bundler for fair comparison
@bobzhang yes the output has not went through google closure for optimizations (:optimization
was set to :none
).
Scala.js:
package helloworld
import scala.scalajs.js
import js.annotation._
object HelloWorld extends js.JSApp {
val range = 1000000000
def testProg(n: Int): Int = {
val lmt = (n + 100000000) min range
def loop(i: Int): Int =
if (i >= lmt) i
else loop(i + 1)
loop(n)
}
def main() {
testProg(100000)
}
}
Relevant part of the produced code:
$c_Lhelloworld_HelloWorld$.prototype.testProg__I__I = (function(n) {
var x = ((100000000 + n) | 0);
var that = this.range$1;
var lmt = ((x < that) ? x : that); // <-- optimized `min` here
return this.loop$1__p1__I__I__I(n, lmt)
});
$c_Lhelloworld_HelloWorld$.prototype.loop$1__p1__I__I__I = (function(i, lmt$1) {
_loop: while (true) {
if ((i >= lmt$1)) {
return i
} else {
i = ((1 + i) | 0);
continue _loop
}
}
});
But in the above code, min
is not really curried (it does have a few indirections, as an extension method of primitive Int
s). Currying is not typical of Scala code. Here is a variant with an explicitly defined, truly curried min
function:
def min(a: Int): Int => Int = b => Math.min(a, b)
def testProg(n: Int): Int = {
val lmt = min(n + 100000000)(range)
def loop(i: Int): Int =
if (i >= lmt) i
else loop(i + 1)
loop(n)
}
$c_Lhelloworld_HelloWorld$.prototype.min__I__F1 = (function(a) {
return new $c_sjsr_AnonFunction1().init___sjs_js_Function1((function(a$1) {
return (function(b$2) {
var b = (b$2 | 0);
return ((a$1 < b) ? a$1 : b)
})
})(a))
});
$c_Lhelloworld_HelloWorld$.prototype.testProg__I__I = (function(n) {
var lmt = this.min__I__F1(((100000000 + n) | 0)).apply$mcII$sp__I__I(this.range$1);
return this.loop$1__p1__I__I__I(n, lmt)
});
$c_Lhelloworld_HelloWorld$.prototype.loop$1__p1__I__I__I = (function(i, lmt$1) {
... // same
});
Because currying is not common in Scala, Scala.js won't specifically try to optimize for it. However, it you ask it nicely (by putting @inline
on min
), you get the same code as before. So it is able to optimize currying away; it just doesn't have the heuristics to do it by default, because it's not typical.
@sjrd thanks for your nice example, currying optimization is a quite advanced optimization (and important to curry based languages), it needs more sophisticated analysis, inlining can only solve some very simple cases.
HERE IS IDRIS (with https://github.com/rbarreiro/idrisjs as backend):
module Main
range : Int
range = 1000000000
testProg : Int -> Int
testProg n = loop n
where
lmt : Int
lmt = min (n + 100000000) range
loop : Int -> Int
loop i = if i >= lmt then i else loop (i + 1)
main : IO()
main = printLn $ testProg 0
INTO (Prelude functions have been removed due to Idris uses WPO)
var idris_Main_46_testProg_58_lmt_58_0 =
(function(){
return function(idris__123_e_95_0_125_){
return idris_Prelude_46_Interfaces_46_Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33_min_58_0((idris__123_e_95_0_125_ + 100000000))(1000000000)
}
})()
var idris_Main_46_testProg_58_loop_58_0 =
(function(){
return function(idris__123_e_95_0_125_){
return function(idris__123_e_95_1_125_){
while(true){
var cgIdris_2 = idris_Prelude_46_Interfaces_46_Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33__62__61__58_0(idris__123_e_95_1_125_)(idris_Main_46_testProg_58_lmt_58_0(idris__123_e_95_0_125_));
switch(cgIdris_2[0]){
case 0:
var cgIdris_3 = idris__123_e_95_0_125_;
var cgIdris_4 = (idris__123_e_95_1_125_ + 1);
idris__123_e_95_0_125_ = cgIdris_3;
idris__123_e_95_1_125_ = cgIdris_4;
continue;
break;
case 1:
return idris__123_e_95_1_125_;
break;
}
;
break
}
}
}
})()
var idris_Main_46_main =
(function(){
return idris_Prelude_46_Interactive_46_putStr_39_(null)((idris_Prelude_46_Show_46_primNumShow(null)(idris_prim_95__95_toStrInt)([0])(idris_Main_46_testProg_58_loop_58_0(0)(0)) + "\n"))
})()
After some optimization:
function q_Main$$main(){
return q_Prelude$Interactive$$putStr_39_(null, (q_Prelude$Show$$primNumShow(null, u_prim____toStrInt, [0], x_Main_46_testProg_58_loop_58_0(0, 0)) + "\n"))
}
function _runMain$0(){
return js_idris_force(q_Main$$main()(null))
}
function x_Main_46_testProg_58_loop_58_0(_e$0, _e$1){
while(true){
var cgIdris_2 = q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33__62__61__58_0(_e$1, x_Main_46_testProg_58_lmt_58_0(_e$0));
switch(cgIdris_2[0]){
case 0:
var cgIdris_3 = _e$0;
var cgIdris_4 = (_e$1 + 1);
_e$0 = cgIdris_3;
_e$1 = cgIdris_4;
continue;
break;
case 1:
return _e$1;
break;
}
;
break
}
}
function x_Main_46_testProg_58_lmt_58_0(_e$0){
return q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33_min_58_0((_e$0 + 100000000), 1000000000)
}
Continue
function x_Main_46_testProg_58_lmt_58_0(_e$0){
return q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33_min_58_0((_e$0 + 100000000), 1000000000)
}
function x_Main_46_testProg_58_loop_58_0(_e$0, _e$1){
while(true){
var cgIdris_2 = q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33__62__61__58_0(_e$1, x_Main_46_testProg_58_lmt_58_0(_e$0));
if((cgIdris_2 === 0)) {
var cgIdris_3 = _e$0;
var cgIdris_4 = (_e$1 + 1);
_e$0 = cgIdris_3;
_e$1 = cgIdris_4;
continue
} else {
return _e$1
}
;
break
}
}
var q_Main$$main = (function(){
return q_Prelude$Interactive$$putStr_39_(null, (q_Prelude$Show$$primNumShow(null, u_prim____toStrInt, 0, x_Main_46_testProg_58_loop_58_0(0, 0)) + "\n"))
})()
var _runMain$0 = (function(){
return js_idris_force(q_Main$$main(null))
})()
Continue: Specialize constructor, test and projector of Bool
and Maybe
.
function x_Main_46_testProg_58_lmt_58_0(_e$0){
return q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33_min_58_0((_e$0 + 100000000), 1000000000)
}
function x_Main_46_testProg_58_loop_58_0(_e$0, _e$1){
while(true){
var cgIdris_2 = q_Prelude$Interfaces$$Prelude_46_Interfaces_46__64_Prelude_46_Interfaces_46_Ord_36_Int_58__33__62__61__58_0(_e$1, x_Main_46_testProg_58_lmt_58_0(_e$0));
if(( !cgIdris_2)) {
var cgIdris_3 = _e$0;
var cgIdris_4 = (_e$1 + 1);
_e$0 = cgIdris_3;
_e$1 = cgIdris_4;
continue
} else {
return _e$1
}
;
break
}
}
var q_Main$$main = (function(){
return q_Prelude$Interactive$$putStr_39_(null,
(q_Prelude$Show$$primNumShow(null, u_prim____toStrInt, 0,
x_Main_46_testProg_58_loop_58_0(0, 0)) + "\n"))
})()
var _runMain$0 = (function(){
return js_idris_force(q_Main$$main(null))
})()
In ClojureScript:
compiles to