# Union types in BuckleScript

Union types describe a value that can be one of several types. In JS, it is common to use the vertical bar (|) to separate each type, so `number | string | boolean`

is the type of a value that can be a number, a string, or a boolean.

Following the last post since the introduction of unboxed attributes in `7.1.0`

, we can create such types as follows:

```
type t =
| Any : 'a -> t
[@@unboxed]
let a (v : a) = Any v
let b (v : b) = Any v
let c (v : c) = Any v
```

```
[@unboxed]
type t =
| Any('a): t;
let a = (v: a) => Any(v);
let b = (v: b) => Any(v);
let c = (v: c) => Any(v);
```

Note: due to the `unboxed`

attribute, `Any a`

shares the same runtime representation as `a`

; however, we need to make sure that user can only construct values of type `a`

, `b`

, or `c`

into type `t`

. By making use of the module system, we can achieve this:

```
module A_b_c : sig
type t
val a : a -> t
val b : b -> t
val c : c -> t
end= struct
type t =
| Any : 'a -> t
[@@unboxed]
let a (v : a) = Any v
let b (v : b) = Any v
let c (v : c) = Any v
end
```

```
module A_b_c: {
type t;
let a: a => t;
let b: b => t;
let c: c => t;
} = {
[@unboxed]
type t =
| Any('a): t;
let a = (v: a) => Any(v);
let b = (v: b) => Any(v);
let c = (v: c) => Any(v);
};
```

What happens when we need to know specifically whether we have a value of type `a`? This is a case by case issue; it depends on whether there are some intersections in the runtime encoding of `a`, `b` or `c`. For some primitive types, it is easy enough to use `Js.typeof` to tell the difference between, e.g, `number` and `string`.
Like type guards in typescript, we have to trust the user knowledge to differentiate between union types. However, such user level knowledge is isolated in a single module so that we can reason about its correctness locally.

Let's have a simple example, `number_or_string`

first:

```
module Number_or_string : sig
type t
type case =
| Number of float
| String of string
val number : float -> t
val string : string -> t
val classify : t -> case
end = struct
type t =
| Any : 'a -> t
[@@unboxed]
type case =
| Number of float
| String of string
let number (v : float) = Any v
let string (v : string) = Any v
let classify (Any v : t) : case =
if Js.typeof v = "number" then Number (Obj.magic v : float)
else String (Obj.magic v : string)
end
```

```
module Number_or_string: {
type t;
type case =
| Number(float)
| String(string);
let number: float => t;
let string: string => t;
let classify: t => case;
} = {
[@unboxed]
type t =
| Any('a): t;
type case =
| Number(float)
| String(string);
let number = (v: float) => Any(v);
let string = (v: string) => Any(v);
let classify = (Any(v): t): case =>
if (Js.typeof(v) == "number") {
Number(Obj.magic(v): float);
} else {
String(Obj.magic(v): string);
};
};
```

Note that here we use `Obj.magic`

to do an unsafe type cast which relies on `Js.typeof`

. In practice, people may use `instanceof`

; the following is an imaginary example:

```
module A_or_b : sig
type t
val a : a -> t
val b : b -> t
type case =
| A of a
| B of b
val classify : t -> case
end = struct
type t =
| Any : 'a -> t
[@@unboxed]
type case =
| A of a
| B of b
let a (v : a) = Any v
let b = (v : b) = Any v
let classify ( Any v : t) =
if [%raw{|function (a) { return a instanceof globalThis.A}|}] v then A (Obj.magic v : a)
else B (Obj.magic b)
end
```

```
module A_or_b: {
type t;
let a: a => t;
let b: b => t;
type case =
| A(a)
| B(b);
let classify: t => case;
} = {
[@unboxed]
type t =
| Any('a): t;
type case =
| A(a)
| B(b);
let a = (v: a) => Any(v);
let b = (v: b) => Any(v);
let classify = (Any (v): t) =>
if ([%raw {|function (a) { return a instanceof globalThis.A}|}](v)) {
A(Obj.magic(v): a);
} else {
B(Obj.magic(b));
};
};
```

Here we suppose `a`

is of JS class type `A`

, and we use `instanceof`

to test it. Note we use some `unsafe`

code locally, but as long as such code is carefully reviewed, it has a safe boundary at the module level.

To conclude: thanks to `unboxed`

attributes and the module language, we introduce a systematic way to convert values from `union types`

(untagged union types) to `algebraic data types`

(tagged union types). This sort of conversion relies on user level knowledge and has to be reviewed carefully. For some cases where `classify`

is not needed, it can be done in a completely type safe way.