Compiler ยท Dec 20, 2019

Announcing BuckleScript 7.0.2-dev.1

Hongbo Zhang
Compiler Team

Happy Holiday Release!

bs-platform@7.0.2-dev.1 is released for testing!

Try it via

npm i -g bs-platform@7.0.2-dev.1

This release contains several bug fixes for refmt(updated from 3.5.1 to 3.5.4). We also spent quite some time improving the compiler performance. For example, we optimized our specialized hash based data structures, which means that we can expect a 5% better build time performance. We would like to collect more benchmark data, so we are happy for any feedback / benchmarks from our community!

A highlighting feature is that we added Generalized Unboxed Support (so called [@unboxed] annotations). Here's a short definition from the official OCaml Manual:

unboxed can be used on a type definition if the type is a single-field record or a concrete type with a single constructor that has a single argument. It tells the compiler to optimize the representation of the type by removing the block that represents the record or the constructor (i.e. a value of this type is physically equal to its argument). In the case of GADTs, an additional restriction applies: the argument must not be an existential variable, represented by an existential type variable, or an abstract type constructor applied to an existential type variable.

Note: The beforementioned restriction about GADTs only applies to OCaml's native compiler, not to BuckleScript's JavaScript compilation. So we will get the maximum value with less confusing error messages!

The exciting thing about this feature is that we will now have more ways of expressing our programs in our typical type safe records and variants without sacrificing on runtime performance ("zero cost interop").

The best way to understand this feature is by looking at the following examples:

Unboxed variants:

RE
[@unboxed] type t = A(int); let x = A(3);

will translate to following JS:

JS
var x = 3;

As you can see, we are "unboxing" the int value from the internal variant representation, so the variant will get completely invisible to the runtime. Great for e.g. mapping to stringly typed JavaScript enums!

Unboxed Records (1 field only)

RE
[@unboxed] type t2 = {f: string}; let x = {f: "foo"};

will translate to following JS:

JS
var x = "foo";

The same principle as with variants. Now a lot of people will probably ask: "Why would I ever want a 1 field record?". There are multiple reasons, one of them would be a ref type, which is just a syntax sugar for a { contents: 'a} record.

Another use case is for expressing high rank polymorphism without cost:

RE
[@unboxed] type r = {f: 'a. 'a => 'a}; let map_pair = (r, (p1, p2)) => (r.f(p1), r.f(p2));

Note: 'a. 'a => 'a describes a polymorphic function interface, where 'a can be called with many different types (e.g. f(1) and f("hi")). The compiler will not try to lock 'a for the first type it sees (e.g. the int) on the first call site. The parameter 'a is therefore polymorphic!

By unboxing those records with one polymorphic function, we will get rid of value restriction for our existing encoding of uncurried function, this will be a major feature!

Unboxed GADTs:

Since GADTs are lesser known in Reason syntax, we also added some OCaml snippet to get a better idea of how the example data structure is defined.

RE
[@unboxed] type t = | Any ('a) : t; let array = [|Any(3), Any("a")|];
ML
(* OCaml *) type t = | Any : 'a -> t [@@unboxed] let array = [|Any 3; Any "a"|]

The examples above will translate to following JS:

JS
var array = [ 3, "a"];

As you can already tell, this feature will give us way better possibilities to do interop with polymorphic array representations in JavaScript (without losing any type safetiness!).

As a more concrete use-case, this will give users the possibility to define types such as int_or_string.

Note: Even if this GADT t contains an ADT Any, it doesn't mean that it's the same as any in TypeScript. An Any value is constrained to a certain contract ('a -> t), the array [|Any(3), Any("a")|] is inferred as a array(t). When users try to use Any values, they need to unpack them, process the value inside, and repack them again. Pretty neat, right?

Conclusion

This release will introduce the [@unbox] annotation to give us better ways to do zero cost interop with variants, records, higher kinded polymorphic functions, and GADTs. Under the hood improvements will give us better performance as well!

We are really excited about these changes, and we hope so are you. Please check out our newest bs-platform@7.0.2-dev.1 release and let us know if you find any issues!

A detailed list of changes is available here: https://github.com/BuckleScript/bucklescript/blob/master/Changes.md#702

Happy hacking!

Appendix

A sophiscated explanation on why unboxed lifts some OCaml's type system limitations

structural types (objects, classes, polymorphic variants, functions, etc) in OCaml are regular types, ocaml always do the expansion when dealing with such types, there is some limitations for such structural types, for example, non regular definitions are not allowed. Non structural types (variants, records) does not have such limitations, with unboxed, we can use non structural types as an indirection without changing its runtime representations.

This article was originally released on https://bucklescript.github.io/blog/2019/12/20/release-7-02
Want to read more?
Back to Overview