Apr 13, 2020

Announcing BuckleScript 7.3

Featuring major improvements like Generalized Uncurry Convention Support and unit value to undefined compilation.

Hongbo Zhang
Compiler & Build System

Important: This is an archived blog post, kept for historical reasons. Please note that this information might be outdated.

Overview

We are happy to announce that bs-platform@7.3 is available for testing, you can try it with npm install bs-platform@7.3.1.

For those unfamiliar with bs-platform, it is the platform for compiling ReasonML and OCaml to fast and readable JavaScript.

This is a major release with some highlighted features as below:

Generalized uncurry calling convention support

You can use an uncurried function as conveniently as a curried one now, this is an exciting change that we wrote a separate post for details.

For uncurried support, we also fixed a long standing issue so that type inference follows naturally using the new encoding.

RE
bar -> Belt.Array.mapU((.b)=>b.foo /*no type annotation needed */)

The unit value now compiles to undefined

In ReasonML, when a function does not return any meaningful value, it returns a value that is () of type unit. In native backend, the dummy value () is compiled into a const zero. We used to inherit this in JS backend as well. However, this is a semantics mismatch since in JS, if the function does not return anything, it defaults to undefined. In this release, we make it more consistent with JS: compiling () into undefined. Since in JS, return undefined can be ignored in tail position, this leads to some other nice enhancement.

RE
let log = x => Js.log(x)

The generated code used to be

JS
function log(x){ console.log(x); return /* () */ 0; }

It's now

JS
function log(x){ console.log(x) }

Various improvements in code generation

We have increased the readability of the generated code in several common places, we believe that we reached an important milestone that if you write code using features that have counterparts in JS, the generated code is readable. This is not a small achievement given that quite a lot of the compiler code base is shared between native backend and JS backend.

There are some features that are not available in JS, for example, complex pattern matches, the readability of those pieces of generated code will continue being improved.

Take several enhancement below as examples:

Meaningful pattern match variable names

RE
let popUndefined = s => switch (s.root) { | None => Js.undefined | Some(x) => s.root = x.tail; Js.Undefined.return(x.head); };
DIFF
function popUndefined(s) { - var match = s.root; - if (match !== null) { - s.root = match.tail; - return match.head; + var x = s.root; + if (x !== undefined) { + s.root = x.tail; + return x.head; } }

When pattern match against a compounded expression, the compiler used to use a temporary name match, now we employ better heuristics to generate meaningful names for such temporary variables.

Eliminate intermediate variable names when inlining

DIFF
function everyU(arr, b) { var len = arr.length; - var arr$1 = arr; var _i = 0; - var b$1 = b; - var len$1 = len; while(true) { var i = _i; - if (i === len$1) { + if (i === len) { return true; - } else if (b$1(arr$1[i])) { - _i = i + 1 | 0; - continue ; - } else { + } + if (!b(arr[i])) { return false; } + _i = i + 1 | 0; + continue ; }; }

The above diff is the generated code for Belt.Array.everyU, the intermediate variables were introduced when inlining an auxiliary function, such duplication were removed in this release.

Flatten if/else branch making use of JS's early return idiom

Take the same diff from above, you will notice that the second else following if(..) continue is removed.

Below are similar diffs benefiting from such enhancement:

DIFF
function has(h, key) { @@ -133,21 +123,18 @@ function has(h, key) { var nid = Caml_hash_primitive.caml_hash_final_mix(Caml_hash_primitive.caml_hash_mix_string(0, key)) & (h_buckets.length - 1 | 0); var bucket = h_buckets[nid]; if (bucket !== undefined) { - var key$1 = key; var _cell = bucket; while(true) { var cell = _cell; - if (cell.key === key$1) { + if (cell.key === key) { return true; - } else { - var match = cell.next; - if (match !== undefined) { - _cell = match; - continue ; - } else { - return false; - } } + var nextCell = cell.next; + if (nextCell === undefined) { + return false; + } + _cell = nextCell; + continue ; }; } else { return false; @@ -155,17 +142,17 @@ function has(h, key) { }
DIFF
--- a/lib/js/belt_List.js +++ b/lib/js/belt_List.js @@ -15,9 +15,8 @@ function head(x) { function headExn(x) { if (x) { return x[0]; - } else { - throw new Error("headExn"); } + throw new Error("headExn"); }

For loop minor-enhancement

DIFF
function shuffleInPlace(xs) { var len = xs.length; - for(var i = 0 ,i_finish = len - 1 | 0; i <= i_finish; ++i){ + for(var i = 0; i < len; ++i){ swapUnsafe(xs, i, Js_math.random_int(i, len)); } - return /* () */0; + }

Reason's for .. in only provide closed interval iterating, so it is quite common to write for (i in 0 to Array.length(x) - 1) { .. }, we did the tweaking above to make the generated code more readable.

A full list of changes is available in our Changelog file.

Want to read more?
Back to Overview