Safe JavaScript interop in Reasonml

The good part

Reasonml let's you call JavaScript functions and pass values around very easily.

As the reason doc says:

Just dumping JavaScript in the middle of your Reason code

The bad part

Due to JavaScript's very dynamic nature, you might accidentally call a function which doesn't exists or who throws an exception at runtime.

Usually in functional programming languages every interaction with the outside world is handled in a safe way (using a Monad for example).
In Reasonml I was a bit surprise that you are allowed to directly interact with side effects (JavaScript 💔).

Concretely if you typed your function which is invoking some JavaScript code and it throws during its execution, the resulting type won't be correct.

Introducing the Either type

The Either data structure is a sort of Monad with two possible values. It represents the result of a computation (left branch) or its failure (right branch).
For example Either a b is either Left a or Right b.

I decided to use a Tuple for the backend of my Either (why is detailed later). Here is its type definition:

Reasonml
        
type either 'l 'r = ('l, 'r);
        
      

Example

For the sake of readability I injected a few JavaScript helpers.

Reasonml
        
[%%bs.raw {|
  function success(v) { return [v, ""] };
  function failwith(e) { return ["", e] };
|}];
        
      

We need a small interop layer to transform the JavaScript output to a Reasonml value.

Reasonml
        
type eval =
  | Success string
  | Error string;

let runtime_to_either v =>
  switch v {
  | (l, "") => Success l
  | ("", r) => Error r
  | (_, _) => failwith "Could not parse JavaScript output"
  };
        
      

I will declare some JavaScript computation examples, this part is up to you since it depends on what your application does.
Looking at the type definitions, it feels very natural; each of these computations return either the result or an error.

Reasonml
        
let testJsThrows: string => either string string = [%bs.raw
  {|
  (function (x) {
    try {
      throw "Error";
      return success(x);
    } catch (e) {
      return failwith(e);
    }
  })
|}
];

let testJsSucceed: string => either string string = [%bs.raw
  {|
  (function (x) {
    try {
      return success(x);
    } catch (e) {
      return failwith(e);
    }
  })
|}
];
        
      
Reasonml
        
switch (testJsThrows "test" |> runtime_to_either) {
| Success x => Js.log2 "testJsThrows" x
| Error e => Js.log2 "testJsThrows" e
};

switch (testJsSucceed "test" |> runtime_to_either) {
| Success x => Js.log2 "testJsSucceed" x
| Error e => Js.log2 "testJsSucceed" e
};
        
      

Which outputs:

Shell
        
$ node lib/js/src/demo.js
testJsThrows Error
testJsSucceed test
        
      
You can find the full code here.

Why a tuple?

The main issue of the Tuple type is that it only allows one type. That means that the success must have the same type as the error. It defeats a bit the use of the Either.
Ideally, I would use a variant type but its representation at runtime is subject to change in Bucklescript.

Either implementation

My implementation of the Either type was extremely simplified. You can find one in OCaml here.

Reach out

Say hello: [email protected].

Ping me on Twitter: @svensauleau.