How to bind BuckleScript/ReasonML objects to JavaScript objects.
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
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.
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
.
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.