Data types and runtime representation
This is how each Melange type is converted into JavaScript values:
Melange | JavaScript |
---|---|
int | number |
nativeint | number |
int32 | number |
float | number |
string | string |
array | array |
tuple (3, 4) | array [3, 4] |
bool | boolean |
Js.Nullable.tJs.Nullable.t | null / undefined |
Js.Re.tJs.Re.t | RegExp |
Option.t None | undefined |
Option.t Some( Some .. Some (None)) Some(Some( .. Some(None))) | internal representation |
Option.t Some 2 Some(2) | 2 |
record {x = 1; y = 2} {x: 1; y: 2} | object {x: 1, y: 2} |
int64 | array of 2 elements [high, low] high is signed, low unsigned |
char | 'a' -> 97 (ascii code) |
bytes | number array |
list [] | 0 |
list [ x; y ] [x, y] | { hd: x, tl: { hd: y, tl: 0 } } |
variant | See below |
polymorphic variant | See below |
Variants with a single non-nullary constructor:
type tree = Leaf | Node of int * tree * tree
(* Leaf -> 0 *)
(* Node(7, Leaf, Leaf) -> { _0: 7, _1: 0, _2: 0 } *)
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:
type t = A of string | B of int
(* A("foo") -> { TAG: 0, _0: "Foo" } *)
(* B(2) -> { TAG: 1, _0: 2 } *)
type t =
| A(string)
| B(int);
/* A("foo") -> { TAG: 0, _0: "Foo" } */
/* B(2) -> { TAG: 1, _0: 2 } */
Polymorphic variants:
let u = `Foo (* "Foo" *)
let v = `Foo(2) (* { NAME: "Foo", VAL: "2" } *)
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:
let () = Js.log "你好"
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:
let () = Js.log {js|你好,
世界|js}
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):
let world = {j|世界|j}
let helloWorld = {j|你好,$world|j}
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:
let () = React.useEffect2 (fun () -> None) (foo, bar)
let () = React.useEffect2(() => None, (foo, bar));
Will produce:
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:
let r = [%mel.re "/b/g"]
let r = [%mel.re "/b/g"];
Will compile to:
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
andJs.Nullable.toOption
functions in theJs.Nullable
moduleJs.Nullable
module to transform them into eithernull
orundefined
values. - List (also a variant type): use
Array.of_list
andArray.to_list
in theStdlib.Array
moduleStdlib.Array
module. - Character
- Int64
- Lazy values