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'.
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 ];
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.