ReasonML evaluation order gotcha


#1
let foo = (a, b) => {
    print_string(string_of_int(a) ++ " " ++ string_of_int(b));
};
let x = ref(0);
let bar = () => {
  x := x^ + 1;
  x^
};
foo(bar(), bar());

The code above produces different results between Bucklescript and js_of_ocaml.

Bucklescript: 1 2
js_of_ocaml: 2 1

This is painful, since it means it’s my responsibility as a developer
to keep track of what functions that have side-effects,
and to work-around the problem using temporaries, e.g.:

let a = bar();
let b = bar();
foo(a, b);

Would it be possible to improve the developer experience in ReasonML?

Here comes an idea I would like to discuss with the ReasonML community:

IF the compiler could detect if a function could possibly have side-effects,
THEN for any expressions containing multiple calls to such functions,
the evaluation order could be enforced to always be left-to-right.

ELSE IF the expression only contains calls to pure functions,
THEN the compiler could evaluate them in any order, perhaps even in parallell
in a future multi-core ReasonML compiler.

I don’t know much about the ReasonML compiler, so I don’t know if
the idea above is realistic or not.

If it’s not, then maybe at least it would be possible to develop
a new compiler warning that would detect expressions containing
multiple calls to functions that could potentially have side-effects.

Thoughts?


#2

rtop uses bytecode compilation of Toploop module
sketch.sh uses jsoo compilation (from bytecode) of Toploop module


#3

My apologies, I incorrectly assumed they both must be using js_of_ocaml, thanks for clarifying.

Are the different stages of the compilation process using this “Toploop module” described somewhere?


#4

I wrote a blog awhile ago about the confusing ReasonML toolchain

It could be summarized with this image:

So Bucklescript and js_of_ocaml are both ocaml to js compiler but they work at different stage of the compiler stack so different behavior.


#5

Thanks @thangngoc89 for the nice explanation of the toolchain.

Any comments on the technical feasibility of the ideas I had would be highly appreciated.

Maybe more realistic in the short-term would be a new compiler warning to alert the developer if there are expressions where order of evaluation could matter, i.e. for expressions with multiple calls to functions that could potentially have side-effects.


#6

The order of evaluation of arguments is explicitly undefined in OCaml. See the second paragraph in the section titled “Function application”:

The order in which the expressions expr, argument1, …, argumentn are evaluated is not specified.

I also don’t think any order is obvious to such a degree that it should be assumed without checking the documentation. And given the significant effort that would be required to implement this, and/or the high likelihood of false positives, compared to the relatively small amount of effort needed to work around it by explicitly ordering it like you showed, it doesn’t seem likely that this will be given priority over most other work.

But OCaml, Reason and BuckleScript are all open source projects, so if you put in the work to implement it yourself, and can convince the maintainers that the benefits (including complexity and maintenance costs and such) outweigh the downsides, you can still get something accepted that wouldn’t have been prioritized by other volunteers.


#7

The fix to the problem is small, yes, but the amount of mental work needed to understand when you have a problem, is much higher.

I don’t have the skills (yet) to implement it myself, but I would be happy to make a financial contribution to anyone with the necessary skills who would also see a value in developing this feature and be willing to take on the project.


#8

The mental effort to just not assume a certain non-obvious order of evaluation does not seem particularly high to me. I still agree that it is a problem, and that a warning would be nice though, if feasible.

You could try posting a bounty on BountySource. There’s a few bounties for Reason there that have succeeded and been paid out. But even if you get someone to do the work, you still have to convince the maintainers to merge it. New features have to be documented, tested and maintained too. it’s not just a one-time cost to implement it.


#9

Thanks, great idea!


#10

Hi @gunner, thanks for bringing it up. At some stage we might need make it enforced order. The thing is that OCaml has multiple backends (bytecode, native, two js backends), even if we enforce an order, it would still cause some surprise since other backends may disagree…


#11

Thanks @bobzhang, I see why it’s non-trivial, I guess it’s situations like one this that leads to RFCs using the key word “RECOMMENDED” instead of “MUST”. :slight_smile:

The compiler warning would also be useful to get a better picture of how much code out there that actually depend on side-effects in a particular order of evaluation.