F# via fable doesn’t run in nashorn yet, but could

Recently, in order to give a coworker an easier way to interact with some code we deliver as javascript, I ported something from bucklescript to fable so that it’d be visible in a .net IDE. Admittedly, the result is pretty swank. Since F# branched from ocaml linguistically, it wasn’t a huge effort (roughly 1 full day of work), but something interesting happened running the resulting javascript on nashorn (note, a late addition here is that I notice that this is mainly due to the es5ification from babel).

Constructing unions in F# would sometimes work and sometimes not, whereas the code ran correctly in node. I had previously tried this and, not wanting to do a deep dive into why fable doesn’t run on nashorn, didn’t investigate very far. Now I know why it happens. Note: the function below isn’t exactly like the one that calls Union, but it clearly illustrates the difference between node and nashorn that makes the bug possible,

arty@clotho:~/dev/corda-web/backend/contract$ jjs
jjs> function f(tag,name) {
if (!tag) {
return Array.prototype.slice.call(arguments);
} else {
return f.call.apply(
f, [f,tag-1,name].
concat(Array.prototype.slice.call(arguments))
);
}
}
jjs> f(0,'hi there')
0,hi there
jjs> f(1,'hi there')
0,hi there,1,hi there
jjs> f(2,'hi there')
0,hi there,1,hi there
jjs> f(3,'hi there')
0,hi there,1,hi there

Here’s the same thing on node:

> f(0,’hi there’) 
f(0,’hi there’)
[ 0, ‘hi there’ ]
> f(1,’hi there’)
f(1,’hi there’)
[ 0, ‘hi there’, 1, ‘hi there’ ]
> f(2,’hi there’)
f(2,’hi there’)
[ 0, ‘hi there’, 1, ‘hi there’, 2, ‘hi there’ ]

So I finally get what’s happening here: union constructors delegate to a Union function to actually construct, slicing off a tag and a name and passing on a reconstituted parameter set to Union.

Weirder still, slightly changing the definition changes the outcome in jjs:

jjs> function f(tag,name) { 
var x = Array.prototype.slice.call(arguments);
if (!tag) {
return Array.prototype.slice.call(arguments);
} else {
return f.call.apply(
f, [f,tag-1,name].
concat(Array.prototype.slice.call(arguments)));
}
}
jjs> f(2,’hi there’)
0,hi there,1,hi there,2,hi there
jjs>

I’ve determined that what I see in nashorn isn’t systematic, but inconsistent, and I believe it’s due to caching, note that order matters:

jjs> function f…
jjs> f(0,’hi there’)
0,hi there
jjs> f(1,’hi there’)
0,hi there,1,hi there
jjs> f(2,’hi there’)
0,hi there,1,hi there

and

jjs> function f…
jjs> f(2,'hi there')
0,hi there,1,hi there,2,hi there
jjs> f(1,'hi there')
0,hi there,1,hi there
jjs> f(0,'hi there')
0,hi there

Happily, it’s possible to work around this particular thing by making fields a named parameter of Union, rather than collecting it at both levels (and it’s more efficient too). I don’t know what else is lurking.