Bindings cookbook
Globals
window
: global variable
external window : Dom.window = "window"
external window: Dom.window = "window";
See the Using global functions or values section for more information.
window?
: does global variable exist
let _ = match [%mel.external window] with
| Some _ -> "window exists"
| None -> "window does not exist"
let _ =
switch ([%mel.external window]) {
| Some(_) => "window exists"
| None => "window does not exist"
};
See the Detect global variables section for more information.
Math.PI
: variable in global module
external pi : float = "PI" [@@mel.scope "Math"]
[@mel.scope "Math"] external pi: float = "PI";
See the Binding to properties inside a module or global section for more information.
console.log
: function in global module
external log : 'a -> unit = "log" [@@mel.scope "console"]
[@mel.scope "console"] external log: 'a => unit = "log";
See the Binding to properties inside a module or global section for more information.
Modules
const path = require('path'); path.join('a', 'b')
: function in CommonJS/ES6 module
external join : string -> string -> string = "join" [@@mel.module "path"]
let dir = join "a" "b"
[@mel.module "path"] external join: (string, string) => string = "join";
let dir = join("a", "b");
See the Using functions from other JavaScript modules section for more information.
const foo = require('foo'); foo(1)
: import entire module as a value
external foo : int -> unit = "foo" [@@mel.module]
let () = foo 1
[@mel.module] external foo: int => unit = "foo";
let () = foo(1);
See the Using functions from other JavaScript modules section for more information.
import foo from 'foo'; foo(1)
: import ES6 module default export
external foo : int -> unit = "default" [@@mel.module "foo"]
let () = foo 1
[@mel.module "foo"] external foo: int => unit = "default";
let () = foo(1);
See the Using functions from other JavaScript modules section for more information.
const foo = require('foo'); foo.bar.baz()
: function scoped inside an object in a module
module Foo = struct
module Bar = struct
external baz : unit -> unit = "baz" [@@mel.module "foo"] [@@mel.scope "bar"]
end
end
let () = Foo.Bar.baz ()
module Foo = {
module Bar = {
[@mel.module "foo"] [@mel.scope "bar"] external baz: unit => unit = "baz";
};
};
let () = Foo.Bar.baz();
It is not necessary to nest the binding inside OCaml modules, but mirroring the structure of the JavaScript module layout makes the binding more discoverable.
See the Binding to properties inside a module or global section for more information.
Functions
const dir = path.join('a', 'b', ...)
: function with rest args
external join : string array -> string = "join" [@@mel.module "path"] [@@mel.variadic]
let dir = join [| "a"; "b" |]
[@mel.module "path"] [@mel.variadic]
external join: array(string) => string = "join";
let dir = join([|"a", "b"|]);
See the Variadic function arguments section for more information.
const nums = range(start, stop, step)
: call a function with named arguments for readability
external range : start:int -> stop:int -> step:int -> int array = "range"
let nums = range ~start:1 ~stop:10 ~step:2
external range: (~start: int, ~stop: int, ~step: int) => array(int) = "range";
let nums = range(~start=1, ~stop=10, ~step=2);
foo('hello'); foo(true)
: overloaded function
external fooString : string -> unit = "foo"
external fooBool : bool -> unit = "foo"
let () = fooString ""
let () = fooBool true
external fooString: string => unit = "foo";
external fooBool: bool => unit = "foo";
let () = fooString("");
let () = fooBool(true);
Melange allows specifying the name on the OCaml side and the name on the JavaScript side (in quotes) separately, so it's possible to bind multiple times to the same function with different names and signatures. This allows binding to complex JavaScript functions with polymorphic behaviour.
const nums = range(start, stop, [step])
: optional final argument(s)
external range : start:int -> stop:int -> ?step:int -> unit -> int array
= "range"
let nums = range ~start:1 ~stop:10 ()
external range: (~start: int, ~stop: int, ~step: int=?, unit) => array(int) =
"range";
let nums = range(~start=1, ~stop=10, ());
When an OCaml function takes an optional parameter, it needs a positional parameter at the end of the parameter list to help the compiler understand when function application is finished and when the function can actually execute. This might seem cumbersome, but it is necessary in order to have out-of-the-box curried parameters, named parameters, and optional parameters available in the language.
mkdir('src/main', {recursive: true})
: options object argument
type mkdirOptions
external mkdirOptions : ?recursive:bool -> unit -> mkdirOptions = "" [@@mel.obj]
external mkdir : string -> ?options:mkdirOptions -> unit -> unit = "mkdir"
let () = mkdir "src" ()
let () = mkdir "src/main" ~options:(mkdirOptions ~recursive:true ()) ()
type mkdirOptions;
[@mel.obj] external mkdirOptions: (~recursive: bool=?, unit) => mkdirOptions;
external mkdir: (string, ~options: mkdirOptions=?, unit) => unit = "mkdir";
let () = mkdir("src", ());
let () = mkdir("src/main", ~options=mkdirOptions(~recursive=true, ()), ());
See the Objects with static shape (record-like): Using external functions section for more information.
forEach(start, stop, item => console.log(item))
: model a callback
external forEach :
start:int -> stop:int -> ((int -> unit)[@mel.uncurry]) -> unit = "forEach"
let () = forEach ~start:1 ~stop:10 Js.log
external forEach:
(~start: int, ~stop: int, [@mel.uncurry] (int => unit)) => unit =
"forEach";
let () = forEach(~start=1, ~stop=10, Js.log);
When binding to functions with callbacks, you'll want to ensure that the callbacks are uncurried. [@mel.uncurry]
is the recommended way of doing that. However, in some circumstances you may be forced to use the static uncurried function syntax. See the Binding to callbacks section for more information.
Objects
const person = {id: 1, name: 'Alice'}
: create an object
For quick creation of objects (e.g. prototyping), one can create a Js.t
object literal directly:
let person = [%mel.obj { id = 1; name = "Alice" }]
let person = {
"id": 1,
"name": "Alice",
};
See the Using Js.t
objects section for more information.
Alternatively, for greater type accuracy, one can create a record type and a value:
type person = { id : int; name : string }
let person = { id = 1; name = "Alice" }
type person = {
id: int,
name: string,
};
let person = {
id: 1,
name: "Alice",
};
See the Using OCaml records section for more information.
person.name
: get a prop
let name = person##name
let name = person##name;
Alternatively, if person
value is of record type as mentioned in the section above:
let name = person.name
let name = person.name;
person.id = 0
: set a prop
external set_id : person -> int -> unit = "id" [@@mel.set]
let () = set_id person 0
[@mel.set] external set_id: (person, int) => unit = "id";
let () = set_id(person, 0);
const {id, name} = person
: object with destructuring
type person = { id : int; name : string }
let person = { id = 1; name = "Alice" }
let { id; name } = person
type person = {
id: int,
name: string,
};
let person = {
id: 1,
name: "Alice",
};
let {id, name} = person;
Classes and OOP
In Melange it is idiomatic to bind to class properties and methods as functions which take the instance as just a normal function argument. So e.g., instead of
const foo = new Foo();
foo.bar();
You will write:
let foo = Foo.make ()
let () = Foo.bar foo
let foo = Foo.make();
let () = Foo.bar(foo);
Note that many of the techniques shown in the Functions section are applicable to the instance members shown below.
const foo = new Foo()
: call a class constructor
module Foo = struct
type t
external make : unit -> t = "Foo" [@@mel.new]
end
let foo = Foo.make ()
module Foo = {
type t;
[@mel.new] external make: unit => t = "Foo";
};
let foo = Foo.make();
Note the abstract type t
, which we have revisited already in its corresponding section.
A Melange function binding doesn't have the context that it's binding to a JavaScript class like Foo
, so you will want to explicitly put it inside a corresponding module Foo
to denote the class it belongs to. In other words, model JavaScript classes as OCaml modules.
See the JavaScript classes section for more information.
const bar = foo.bar
: get an instance property
module Foo = struct
type t
external make : unit -> t = "Foo" [@@mel.new]
external bar : t -> int = "bar" [@@mel.get]
end
let foo = Foo.make ()
let bar = Foo.bar foo
module Foo = {
type t;
[@mel.new] external make: unit => t = "Foo";
[@mel.get] external bar: t => int = "bar";
};
let foo = Foo.make();
let bar = Foo.bar(foo);
See the Binding to object properties section for more information.
foo.bar = 1
: set an instance property
module Foo = struct
type t
external make : unit -> t = "Foo" [@@mel.new]
external setBar : t -> int -> unit = "bar" [@@mel.set]
end
let foo = Foo.make ()
let () = Foo.setBar foo 1
module Foo = {
type t;
[@mel.new] external make: unit => t = "Foo";
[@mel.set] external setBar: (t, int) => unit = "bar";
};
let foo = Foo.make();
let () = Foo.setBar(foo, 1);
foo.meth()
: call a nullary instance method
module Foo = struct
type t
external make : unit -> t = "Foo" [@@mel.new]
external meth : t -> unit = "meth" [@@mel.send]
end
let foo = Foo.make ()
let () = Foo.meth foo
module Foo = {
type t;
[@mel.new] external make: unit => t = "Foo";
[@mel.send] external meth: t => unit = "meth";
};
let foo = Foo.make();
let () = Foo.meth(foo);
See the Calling an object method section for more information.
const newStr = str.replace(substr, newSubstr)
: non-mutating instance method
external replace : substr:string -> newSubstr:string -> string = "replace"
[@@mel.send.pipe: string]
let str = "goodbye world"
let substr = "goodbye"
let newSubstr = "hello"
let newStr = replace ~substr ~newSubstr str
[@mel.send.pipe: string]
external replace: (~substr: string, ~newSubstr: string) => string = "replace";
let str = "goodbye world";
let substr = "goodbye";
let newSubstr = "hello";
let newStr = replace(~substr, ~newSubstr, str);
mel.send.pipe
injects a parameter of the given type (in this case string
) as the final positional parameter of the binding. In other words, it creates the binding with the real signature substr:string -> newSubstr:string -> string -> string
(~substr: string, ~newSubstr: string, string) => string
. This is handy for non-mutating functions as they traditionally take the instance as the final parameter.
It is not strictly necessary to use named arguments in this binding, but it helps readability with multiple arguments, especially if some have the same type.
Also note that it is not strictly need to use mel.send.pipe
, one can use mel.send
everywhere.
See the Calling an object method section for more information.
arr.sort(compareFunction)
: mutating instance method
external sort : 'a array -> (('a -> 'a -> int)[@mel.uncurry]) -> 'a array
= "sort"
[@@mel.send]
let _ = sort arr compare
[@mel.send]
external sort: (array('a), [@mel.uncurry] (('a, 'a) => int)) => array('a) =
"sort";
let _ = sort(arr, compare);
For a mutating method, it's traditional to pass the instance argument first.
Note: compare
is a function provided by the standard library, which fits the defined interface of JavaScript's comparator function.
Null and undefined
foo.bar === undefined
: check for undefined
module Foo = struct
type t
external bar : t -> int option = "bar" [@@mel.get]
end
external foo : Foo.t = "foo"
let _result = match Foo.bar foo with Some _ -> 1 | None -> 0
module Foo = {
type t;
[@mel.get] external bar: t => option(int) = "bar";
};
external foo: Foo.t = "foo";
let _result =
switch (Foo.bar(foo)) {
| Some(_) => 1
| None => 0
};
If you know some value may be undefined
(but not null
, see next section), and if you know its type is monomorphic (i.e. not generic), then you can model it directly as an Option.t
type.
See the Non-shared data types section for more information.
foo.bar == null
: check for null or undefined
module Foo = struct
type t
external bar : t -> t option = "bar" [@@mel.get] [@@mel.return nullable]
end
external foo : Foo.t = "foo"
let _result = match Foo.bar foo with Some _ -> 1 | None -> 0
module Foo = {
type t;
[@mel.get] [@mel.return nullable] external bar: t => option(t) = "bar";
};
external foo: Foo.t = "foo";
let _result =
switch (Foo.bar(foo)) {
| Some(_) => 1
| None => 0
};
If you know the value is 'nullable' (i.e. could be null
or undefined
), or if the value could be polymorphic, then mel.return nullable
is appropriate to use.
Note that this attribute requires the return type of the binding to be an option
type as well.
See the Wrapping returned nullable values section for more information.