Inlined values in BuckleScript
Tags: bucklescript, ocaml, reason
Published: 2019-12-11 04:05:56

---
title: Inlined values in BuckleScript
published: true
description: How to control exactly what values are allowed in a type with minimal runtime impact
tags: bucklescript,ocaml,reason
---

BUCKLESCRIPT ships with a convenient feature that helps you call JavaScript functions more safely: constraining parameter values to certain strings. Suppose you have a JavaScript function (e.g. a React component):

```javascript
// src/rbtree.js

/** @param node a node.
    @param color can be either 'red' or 'black'.
    @return the painted node. */
function paint(node, color) { ... }
```

In BuckleScript you can model this function like so:

```ocaml
// src/Rbtree.re

type node;

[@bs.module "./rbtree"] external paint: (
  node,
  [@bs.string] [`red | `black],
) => node = "";
```

The special type:

```ocaml
[@bs.string] [`red | `black]
```

...tells BuckleScript to accept exactly the following values:

```ocaml
`red
`black
```

...but convert them into their string representations for the output JavaScript. (These values are called [polymorphic variants](http://reasonmlhub.com/exploring-reasonml/ch_polymorphic-variants.html), by the way.) This way, you control exactly what strings will ultimately be output and guarantee the JavaScript function is called correctly. This is covered in the documentation: https://bucklescript.github.io/docs/en/function#constrain-arguments-better

## The reuse problem

The problem with the above technique is that the special type:

```ocaml
[@bs.string] [`red | `black]
```

...can't be captured as a named type and reused–not even when you leave out the annotation. It's a special syntactic construct that must be typed in literally, every time it's used.

But, multiple functions in a library you're using might want exactly the same set of strings as an input parameter. So you would need to repeat this boilerplate for all of them. For bigger libraries this repetition can get quite cumbersome (and error-prone).

## The 'private type' solution

You can instead define a _private type_ for the color parameter:

```ocaml
module Color: {
  type t = pri string;

  let red: t;
  let black: t;
} = {
  type t = string;

  let red = "red";
  let black = "black";
};

[@bs.module "./rbtree"] external paint: (node, Color.t) => node = "";
```

The type `Color.t` is defined as a 'private string', meaning you can't create values of `Color.t` but you can convert them into `string`s. And, it really is `string` under the hood. Now users can pass in _only_ the defined values (enforced by the compiler): `Color.red` or `Color.black`. And the output values in JavaScript will be exactly `'red'` and `'black'`. And the best part is the `Color.t` is a real type, not a syntax construct, and can be passed around and used across functions.

There is only one small issue with this solution: you are introducing a little bit of extra runtime into your library binding by allocating all the allowed values upfront, and including them in your binding library. When every byte of JavaScript that you ship to the browser matters, it can be quite helpful to be able to minimize that.

## The 'inline' technique

Fortunately, BuckleScript recently shipped with the ability to _explicitly inline_ values: https://bucklescript.github.io/blog/2019/04/09/release-schedule

Note that I say 'explicitly' because BuckleScript has always been rather great at inlining ('constant folding') any values that it can calculate at compile time. E.g. try compiling this: `let test = 2 + 2;`. You'll get the output `var test = 4;`. And this is only a simple example.

So, with the explicit inlining feature you can actually mark your color values as inlined:

```ocaml
module Color: {
  type t = pri string;
  
  [@bs.inline "red"] let red: t;
  [@bs.inline "black"] let black: t;
} = {
  type t = string;
  
  [@bs.inline] let red = "red";
  [@bs.inline] let black = "black";
};
```

This tells BuckleScript to inline the usages of `Color.red` and `Color.black`, with the given literal values. If you're wondering if the repetition is really necessary–it is, but it does act as a self-check, so all in all not bad.

Now, the extra runtime introduced by this binding is almost nil. You should expect to see an empty 'module' `Color` in the JavaScript output–that's it. You pay the cost of those values at runtime _only_ when you use them.

By the way, this `[@bs.inline]` attribute supports values of type `string`, `int`, and `bool` only. So it is limited but it should cover quite a lot of the JavaScript literals that you would typically pass in to functions.