Binding to a JavaScript Function that Returns a Variant in ReScript
Tags: javascript, indexeddb, rescript, functional
Published: 2021-06-11 20:50:54

---
title: Binding to a JavaScript Function that Returns a Variant in ReScript
published: true
date: 2021-06-09 00:00:00 UTC
tags: ["javascript", "indexeddb", "rescript", "functional"]
canonical_url: https://webbureaucrat.gitlab.io/articles/binding-to-a-javascript-function-that-returns-a-variant-in-rescript/
---

ReScript provides easy ways to bind to most JavaScript functions in a way that feels both native and safe. Conveniently, it even provides an [`@unwrap`](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#trick-2-polymorphic-variant--unwrap) decorator for parametric polymorphism. However, there are a few places where we still have to fill in the gaps. This article documents how to bind to a JavaScript function that can return any one of several different types using ReScript variants.

### The need for a custom solution

JavaScript is both dynamic and weakly typed, and even the standard libraries take full advantage of those features in ways that can cause headaches for anyone trying to use a static type system.

TypeScript deals with this in a very literal way through union types. That is, the type is literally defined as `OneType | TheOtherType` so that the developer can account for both cases. ReScript does not have union types, but does have [variants](https://rescript-lang.org/docs/manual/latest/variant), which can be abstractions _around_ different types.

Under the hood, these are JavaScript objects with properties that represent the underlying values.

_sample output from the official documentation_

```ocaml
var f1 = /* Child */0;
var f2 = { 
    TAG: /* Mom */0, 
    _0: 30, 
    _1: "Jane"
};
var f3 = { 
    TAG: /* Dad */1, 
    _0: 32
};
```

It's sleek on the ReScript side, but nonnative to JS. This means there's no way under the current variant structure to directly bind to a method like [`IDBObjectStore.keypath`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/keyPath), which could return `null` a string, or an array of strings. We can certainly represent a similar type like

_IDBObjectStoreKeyPath.res_

```ocaml
type t = Null | String(string) | Array(Js.Array.t<string>);
```

...but ReScript will expect that instances of this type will have `TAG` and numbered properties like the sample JavaScript output above. What we need is a way to _classify_ what gets returned by our binding and call the appropriate variant constructor accordingly.

### Writing a binding to a dummy type

We're going to end up doing a bit of unsafe black magic that we don't want our library users to use, so let's wrap it in a module to offset it from the code we'll expose in our _.resi_:

```ocaml
module Private = {
};
```

As we've established, there's no way to directly represent the returned value of `keyPath` in the ReScript type system, so let's not bother.

```ocaml
module Private = { 
    type any; 
    @get external keyPath: t => any = "keyPath";
};
```

Now, let's dig into the ugly stuff.

### Thinking about types in JavaScript

Let's break out of ReScript for a moment and think about the JavaScript runtime side of things. If we were managing this in JavaScript, we would probably use the `typeof` operator to return a string, and then we could branch our logic accordingly.

But we can't only use `typeof` because `typeof null` and `typeof []` both return `"object"`, so we'll need a null check as well.

So if we were doing this in JavaScript, we'd end up with a piece of code something like

```javascript
x => x === null ? "null" : typeof x
```

Let's hold on to that thought.

### Modeling the type _of the type_ in ReScript

Our JavaScript expression above will (for all `IDBObjectStoreKeyPath`s) return "null", "object", or "string". This translates very nicely to a ReScript polymorphic variant, like so:

```ocaml
type typeName = [#null | #"object" | #"string"];
```

So now, with this type, we can type our JavaScript expression in a `%raw`JavaScript snippet:

```ocaml
type typeName = [#null | #"object" | #"string"]; 
let getType: any => typeName = %raw(`x => x === null ? "null" : typeof x`);
```

So now we can get the `keyPath` through the binding, and we can then get the type name of that keyPath. We're so close.

### `magic`ally calling the proper constructor

We have one last step: we need to switch on our `typeName` to call switch on our `typeName`, use `Obj.magic` to convert our type to the proper ReScript type, and then call our constructor, which will wrap our type in our variant.

```ocaml
  let classify = (v: any): IDBObjectStoreKeyPath.t => 
    switch(v -> getType) {
    | #null => IDBObjectStoreKeyPath.Null;
    | #"object" => IDBObjectStoreKeyPath.Array(v -> Obj.magic);
    | #"string" => IDBObjectStoreKeyPath.String(v -> Obj.magic);
    };

```

`Obj.magic` will cast the value to return whatever it infers, but our `switch`should ensure the cast is safe (in practice, though not in theory).

### `classify`ing `any` `keyPath`

Tying it all together, we can now use our `classify` function to sanitize the `any` dummy type returned from our `keyPath` binding.

```ocaml
let keyPath = (t: t): IDBObjectStoreKeyPath.t => 
  t -> Private.keyPath -> Private.classify;
```

(This is the kind of thing that gets me excited about functional programming-- when we break things into small enough pieces, anything seems easy and simple.)

### Wrapping up

I hope this has been a useful resource for writing difficult bindings. Just to review, we were able to successfully return this variant...

_IDBObjectStoreKeyPath.res_

```ocaml
type t = Null | String(string) | Array(Js.Array.t<string>);
```

...from a function called `keyPath` by wrapping the binding like so:

_IDBObjectStore.res_

```ocaml
type t;

module Private = {
  type any;
  @get external keyPath: t => any = "keyPath";
  type typeName = [ #null | #"object" | #"string" ];
  let getType: any => typeName = %raw(`x => x === null ? "null" : typeof x`);
  let classify = (v: any): IDBObjectStoreKeyPath.t => 
    switch(v -> getType) {
    | #null => IDBObjectStoreKeyPath.Null;
    | #"object" => IDBObjectStoreKeyPath.Array(v -> Obj.magic);
    | #"string" => IDBObjectStoreKeyPath.String(v -> Obj.magic);
    };
};

/* properties */

let keyPath = (t: t): IDBObjectStoreKeyPath.t =>
  t -> Private.keyPath -> Private.classify;

```

I hope that this has been helpful for modeling union types using ReScript variants. For my part, I'm sure to refer back to this article as I continue writing and iterating on bindings.