Emulating TypeScript union types with ReasonML, part 2

Yawar Amin - Jun 6 '20 - - Dev Community

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'
Enter fullscreen mode Exit fullscreen mode

Can be modelled with a Reason function:

let labels = fun
  | `DownArrow => "Down Arrow"
  | `LeftArrow => "Left Arrow";
Enter fullscreen mode Exit fullscreen mode

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

let labels: [< `DownArrow | `LeftArrow] => string;
Enter fullscreen mode Exit fullscreen mode

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
];
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .