Skip to content

Data types and runtime representation

This is how each Melange type is converted into JavaScript values:

MelangeJavaScript
intnumber
nativeintnumber
int32number
floatnumber
stringstring
arrayarray
tuple (3, 4)array [3, 4]
boolboolean
Js.Nullable.tJs.Nullable.tnull / undefined
Js.Re.tJs.Re.tRegExp
Option.t Noneundefined
Option.t Some( Some .. Some (None))Some(Some( .. Some(None)))internal representation
Option.t Some 2Some(2)2
record {x = 1; y = 2}{x: 1; y: 2}object {x: 1, y: 2}
int64array of 2 elements [high, low] high is signed, low unsigned
char'a' -> 97 (ascii code)
bytesnumber array
list []0
list [ x; y ][x, y]{ hd: x, tl: { hd: y, tl: 0 } }
variantSee below
polymorphic variantSee below

Variants with a single non-nullary constructor:

ocaml
type tree = Leaf | Node of int * tree * tree
(* Leaf -> 0 *)
(* Node(7, Leaf, Leaf) -> { _0: 7, _1: 0, _2: 0 } *)
reasonml
type tree =
  | Leaf
  | Node(int, tree, tree);
/* Leaf -> 0 */
/* Node(7, Leaf, Leaf) -> { _0: 7, _1: 0, _2: 0 } */

Variants with more than one non-nullary constructor:

ocaml
type t = A of string | B of int
(* A("foo") -> { TAG: 0, _0: "Foo" } *)
(* B(2) -> { TAG: 1, _0: 2 } *)
reasonml
type t =
  | A(string)
  | B(int);
/* A("foo") -> { TAG: 0, _0: "Foo" } */
/* B(2) -> { TAG: 1, _0: 2 } */

Polymorphic variants:

ocaml
let u = `Foo (* "Foo" *)
let v = `Foo(2) (* { NAME: "Foo", VAL: "2" } *)
reasonml
let u = `Foo; /* "Foo" */
let v = `Foo(2); /* { NAME: "Foo", VAL: "2" } */

Let’s see now some of these types in detail. We will first describe the shared types, which have a transparent representation as JavaScript values, and then go through the non-shared types, that have more complex runtime representations.

NOTE: Relying on the non-shared data types runtime representations by reading or writing them manually from JavaScript code that communicates with Melange code might lead to runtime errors, as these representations might change in the future.

Shared types

The following are types that can be shared between Melange and JavaScript almost "as is". Specific caveats are mentioned on the sections where they apply.

Strings

JavaScript strings are immutable sequences of UTF-16 encoded Unicode text. OCaml strings are immutable sequences of bytes and nowadays assumed to be UTF-8 encoded text when interpreted as textual content. This is problematic when interacting with JavaScript code, because if one tries to use some unicode characters, like:

ocaml
let () = Js.log "你好"
reasonml
let () = Js.log("你好");

It will lead to some cryptic console output. To rectify this, Melange allows to define quoted string literals using the js identifier, for example:

ocaml
let () = Js.log {js|你好,
世界|js}
reasonml
let () = Js.log({js|你好,
世界|js});

For convenience, Melange exposes another special quoted string identifier: j. It is similar to JavaScript’ string interpolation, but for variables only (not arbitrary expressions):

ocaml
let world = {j|世界|j}
let helloWorld = {j|你好,$world|j}
reasonml
let world = {j|世界|j};
let helloWorld = {j|你好,$world|j};

You can surround the interpolation variable in parentheses too: {j|你 好,$(world)|j}.

To work with strings, the Melange standard library provides some utilities in the Stdlib.String moduleStdlib.String module. The bindings to the native JavaScript functions to work with strings are in the Js.String moduleJs.String module.

Floating-point numbers

OCaml floats are IEEE 754 with a 53-bit mantissa and exponents from -1022 to 1023. This happens to be the same encoding as JavaScript numbers, so values of these types can be used transparently between Melange code and JavaScript code. The Melange standard library provides a Stdlib.Float moduleStdlib.Float module. The bindings to the JavaScript APIs that manipulate float values can be found in the Js.Float moduleJs.Float module.

Integers

In Melange, integers are limited to 32 bits due to the fixed-width conversion of bitwise operations in JavaScript. While Melange integers compile to JavaScript numbers, treating them interchangeably can result in unexpected behavior due to differences in precision. Even though bitwise operations in JavaScript are constrained to 32 bits, integers themselves are represented using the same floating-point format as numbers, allowing for a larger range of representable integers in JavaScript compared to Melange. When dealing with large numbers, it is advisable to use floats instead. For instance, floats are used in bindings like Js.Date.

The Melange standard library provides a Stdlib.Int moduleStdlib.Int module. The bindings to work with JavaScript integers are in the Js.Int moduleJs.Int module.

Arrays

Melange arrays compile to JavaScript arrays. But note that unlike JavaScript arrays, all the values in a Melange array need to have the same type.

Another difference is that OCaml arrays are fixed-sized, but on Melange side this constraint is relaxed. You can change an array’s length using functions like Js.Array.push, available in the bindings to the JavaScript APIs in the Js.Array moduleJs.Array module.

Melange standard library also has a module to work with arrays, available in the Stdlib.Array moduleStdlib.Array module.

Tuples

OCaml tuples are compiled to JavaScript arrays. This is convenient when writing bindings that will use a JavaScript array with heterogeneous values, but that happens to have a fixed length.

As a real world example of this can be found in ReasonReact, the Melange bindings for React. In these bindings, component effects dependencies are represented as OCaml tuples, so they get compiled cleanly to JavaScript arrays by Melange.

For example, some code like this:

ocaml
let () = React.useEffect2 (fun () -> None) (foo, bar)
reasonml
let () = React.useEffect2(() => None, (foo, bar));

Will produce:

javascript
React.useEffect(function () {}, [foo, bar]);

Booleans

Values of type bool compile to JavaScript booleans.

Records

Melange records map directly to JavaScript objects. If the record fields include non-shared data types (like variants), these values should be transformed separately, and not be directly used in JavaScript.

Extensive documentation about interfacing with JavaScript objects using records can be found in the section below.

Regular expressions

Regular expressions created using the %mel.re extension node compile to their JavaScript counterpart.

For example:

ocaml
let r = [%mel.re "/b/g"]
reasonml
let r = [%mel.re "/b/g"];

Will compile to:

js
var r = /b/g;

A regular expression like the above is of type Js.Re.t. The Js.Re moduleJs.Re module provides the bindings to the JavaScript functions that operate over regular expressions.

Non-shared data types

The following types differ too much between Melange and JavaScript, so while they can always be manipulated from JavaScript, it is recommended to transform them before doing so.

  • Variants and polymorphic variants: Better transform them into readable JavaScript values before manipulating them from JavaScript, Melange provides some helpers to do so.
  • Exceptions
  • Option (a variant type): Better use the Js.Nullable.fromOption and Js.Nullable.toOption functions in the Js.Nullable moduleJs.Nullable module to transform them into either null or undefined values.
  • List (also a variant type): use Array.of_list and Array.to_list in the Stdlib.Array moduleStdlib.Array module.
  • Character
  • Int64
  • Lazy values