DEV Community

loading...

Emulating TypeScript union types with ReasonML, part 2

Yawar Amin
Programming languages enthusiast. Author of Learn Type Driven Development: https://www.packtpub.com/application-development/learn-type-driven-development
・2 min read

IN a previous post, I showed a technique in Reason for automatically creating a 'union type' like the ones that TypeScript has. In essence, this TypeScript union type:

type Labels = 'Down Arrow' | 'Left Arrow'

Can be modelled with a Reason function:

let labels = fun
  | `DownArrow => "Down Arrow"
  | `LeftArrow => "Left Arrow";

...and Reason infers that the function has type:

let labels: [< `DownArrow | `LeftArrow] => string;

Of course, this is not exactly the same–it's more of a way to convert a limited set of polymorphic variant values into a limited set of strings. We can think of it as a 'poor man's union type'.

Slightly richer

But Reason's abilities don't stop there. The BuckleScript compiler actually has a feature, called @bs.deriving jsConverter, that can do a two-way conversion between a polymorphic variant type and string values. Here's the previous example, converted to use it:

[@bs.deriving jsConverter]
type label = [
| [@bs.as "Down Arrow"] `DownArrow
| [@bs.as "Left Arrow"] `LeftArrow
];

If you look at the JavaScript output for this, you'll notice the following functions: labelToJs, labelFromJs. These convert the polymorphic variant values to strings and vice-versa. The cool part is that the labelToJs conversion is exactly what we hand-wrote above, so if you pass in an unsupported value you get a type error:

/* let test = labelToJs(`DownArray);

We've found a bug for you!
OCaml preview 5:22-32

This has type:
  [> `DownArray ]
But somewhere wanted:
  label
The second variant type does not allow tag(s) `DownArray */

We can think of this as not exactly a union type, but a type that is 'isomorphic' to a union type. Pretending for a second that Reason had real union types:

let labelToJs: [`DownArrow | `LeftArrow] => "Down Arrow" | "Left Arrow"

Reason does a lot of things by spreading the workload (so to speak) between types and functions. Data types describe the data and function types describe the relationships between them. And even though Reason doesn't have real union types, we can still take advantage of guarantees from features like @bs.deriving jsConverter to come close.

Discussion (2)

Collapse
hoichi profile image
Sergey Samokhov

Funny how in this case TypeScript’s abstraction is zero-cost where Reason’s is not. No wonder, of course, since TS more closely matches the JS semantics.

Then again, a string representation of variant types is probably ever needed for interop only.

Collapse
yawaramin profile image
Yawar Amin Author

True, and for those we have other techniques.