I was playing a bit with code generation and I noticed an interesting behavior. Take the following simple code (partially stolen from this thread):
module Option = {
let flatMapU = (x, f) => {
switch x {
| Some(x) => f(. x)
| None => None
}
}
}
let reciprocal = (. x) => x == 0. ? None : Some(1. /. x)
let indirect = Option.flatMapU(Some(2.), reciprocal)
switch indirect {
| Some(x) => Js.Console.log(x)
| None => ()
}
let direct = switch Some(2.) {
| Some(x) => Some(reciprocal(. x))
| None => None
}
switch direct {
| Some(x) => Js.Console.log(x)
| None => ()
}
let immediate = Some(2.)
switch immediate {
| Some(x) => Js.Console.log(reciprocal(. x))
| None => ()
}
Nothing particularly exiting, just applying reciprocal
to a value if it’s Some
. FYI: using the re-implementation of Option.flatMapU
produces the same result as using Belt
. Here the esbuild
bundle:
(() => {
// node_modules/@rescript/std/lib/es6/caml_option.js
function some(x2) {
if (x2 === void 0) {
return {
BS_PRIVATE_NESTED_SOME_NONE: 0
};
} else if (x2 !== null && x2.BS_PRIVATE_NESTED_SOME_NONE !== void 0) {
return {
BS_PRIVATE_NESTED_SOME_NONE: x2.BS_PRIVATE_NESTED_SOME_NONE + 1 | 0
};
} else {
return x2;
}
}
function valFromOption(x2) {
if (!(x2 !== null && x2.BS_PRIVATE_NESTED_SOME_NONE !== void 0)) {
return x2;
}
var depth = x2.BS_PRIVATE_NESTED_SOME_NONE;
if (depth === 0) {
return;
} else {
return {
BS_PRIVATE_NESTED_SOME_NONE: depth - 1 | 0
};
}
}
// lib/es6/src/demo.js
function flatMapU(x2, f) {
if (x2 !== void 0) {
return f(valFromOption(x2));
}
}
var $$Option = {
flatMapU
};
function reciprocal(x2) {
if (x2 === 0) {
return;
} else {
return 1 / x2;
}
}
var indirect = flatMapU(2, reciprocal);
if (indirect !== void 0) {
console.log(indirect);
}
var x = 2;
var direct = x !== void 0 ? some(reciprocal(x)) : void 0;
if (direct !== void 0) {
console.log(valFromOption(direct));
}
var immediate = 2;
if (immediate !== void 0) {
console.log(reciprocal(immediate));
}
})();
As you can see, the bundler is able to see through valFromOption
in case of immediate
. However, in the other two cases it’s unable to optimize anything. The direct
case is pretty bad, because instead of removing some
, the branch and valFromOption
, we have all the overhead.
Said that, this is probably more a topic for the optimizer than the rescript compiler, but I am wondering: is there something that could be done to help the optimizer? I am asking because a nice rescript codebase would use option
(and result
) all over the place, and even if the major performance issue are related to everything else (i.e. DOM manipulation), it would be nice if some low-hanging fruits could be available to have better codegen.