How to bind BuckleScript/ReasonML objects to JavaScript objects.

digitake
LambdaSide
Published in
4 min readMay 8, 2018

--

Note1: When I say BuckleScript in this article, I mean OCaml.
Note2. JavaScript object is distinct from Object in BuckleScript/ReasonML.

Despite they support object oriented style natively, BuckleScript/ReasonML are functional languages which means they discourage using a notion of class. Instead, to create a JavaScript object in BuckleScript/ReasonML, one might rather use Js.Dict or Record type. The former should be used when a variable number of keys is needed. The latter is used when the keys are fixed and their types are predetermined.

Create an object using Js.Dict

Create JavaScript object with BuckleScript Js.Dict

Js.Dict is a JavaScript dictionary which we can put anything as a value, however, the strong type system requires its values must be of the same type. The dictionary we created will be directly converted into a plain JavaScript object.

ReasonML counterpart of the snippet above.

If we want to store more than one type of values in Js.Dict, we can do so using nested Js.Dict structure or a variant type. This might seems troublesome but it is a trade-off for the type-safety.

Now, let us see how to store a value of variant type in Js.Dict.

Code snippet shows how to store variant type in a Dictionary object
ReasonML counterpart of above snippet

The values of a Variant type does not store in the JavaScript object “as-is”, but they get converted and stripped out into a plain JavaScript object as shown:

Nothing becomes 0, Something becomes Block.__(0, [1]); and LotOfThing becomes Blocks.__(1, [[1,2,3]]);

This means we will not have the values of a Variant type at runtime. By running the code without looking at the comment, we cannot get back the name of Variant. This is why the annotation accessors is there. It will bind variables according to each variant type so that we can use a Variant in JavaScript side naturally, e.g. myDict[“foo”] = nothing; instead of myDict[“foo”] = /* Nothing */0;

Create an object using Record

While we use Js.Dict to store key and value of the same type(called fields) and it might have a variable number of fields. An object can be defined as a record when it

  • has a known number of fields
  • might or might not contain values of heterogeneous types

Slightly different to Variant, a Record can be defined using type keyword with the following syntax:

type <record-name> =
{ <field> : <type> ;
<field> : <type> ;
...
}

For example,

The code above we produce an array of ["John", 20, "jobless"] which is not quite what we want. To retain the keys, we have to wrap our Record in JavaScript object type Js.t by using object syntax.

type person = < name: string; age: int; job: string > Js.t

The angle bracket here is to create object in BuckleScript. Notice that this does not need a notion of Class as in typical class-based object-oriented languages. For people who came from that world, this might seems strange but in OCaml type is not equal to class. An object can be created without a class.

In order to create, the object of type person we can do so by

let p = object 
method name = "John"
method age = 20
method job = "jobless"
end;;

No, it was not a typo. Attributes we want to expose to external world are defined as methods. This is raw OCaml syntax for object without wrapping in Js.t. However, BuckleScript lifted all JavaScript object to be under Js.t. It helps us avoid this burden of syntax by offering bs.obj annotation. So, the chuck of code above will become

let p = [%bs.obj {name="John"; age=20; job="jobless"}]

ReasonML brings the syntactic sugar to another level. To define a JavaScript binding:

and to create the JavaScript object in ReasonML:

let p = {"name": "John", "age": 20, "job": "jobless"};

Class

JavaScript class, introduced in ES6, is mere function with fancy wiring using a prototype-based inheritance and a function closure.

The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

And their usage is discourage as:

In general, prefer using the previous object section’s features to bind to a JS object.

Lastly, this article aims to talk about binding BuckleScript/ReasonML objects to JavaScript objects but so far what we have done is simply define structures to make OCaml type system recognise objects we are going to use.

external is a keyword to use when we want to bind a value to JavaScript value. For example:

external john : person = "john" [@@bs.val]

This means we bind john to the JavaScript variable name john and it has a type of person.

Conclusion

When working with JavaScript object, one might temping to use it with the box(of Js.t). However, to gain full benefits for OCaml type system, we rather convert Js.t into native structure.

--

--

digitake
LambdaSide

Lambda school of thought, minimalist, mathematical minded. Love AI, Functional, Logic.