diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9dcb9dcab..b7a09ab94 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -118,10 +118,6 @@ export default defineConfig({ { text: "New to OCaml?", link: "/new-to-ocaml" }, { text: "Package Management", link: "/package-management" }, { text: "Build System", link: "/build-system" }, - { - text: "Communicate with JavaScript", - link: "/communicate-with-javascript", - }, { text: "How-to Guides", link: "/how-to-guides" }, { text: "Melange for X Developers", @@ -129,6 +125,18 @@ export default defineConfig({ }, ], }, + { + text: 'Communicate with JavaScript', + items: [ + { text: "Overview", link: "/communicate-with-javascript" }, + { text: "Language concepts", link: "/language-concepts" }, + { text: "Data types and runtime representation", link: "/data-types-and-runtime-rep" }, + { text: "Melange attributes and extension nodes", link: "/attributes-and-extension-nodes" }, + { text: "Working with JavaScript objects and values", link: "/working-with-js-objects-and-values" }, + { text: "Advanced JavaScript interoperability", link: "/advanced-js-interop" }, + { text: "Bindings cookbook", link: "/bindings-cookbook" }, + ], + }, { text: "Reference", items: [{ text: "API", link: "/api" }], diff --git a/docs/advanced-js-interop.md b/docs/advanced-js-interop.md new file mode 100644 index 000000000..dc01f3104 --- /dev/null +++ b/docs/advanced-js-interop.md @@ -0,0 +1,659 @@ +# Advanced JavaScript interoperability + +## Generate getters, setters and constructors + +As we saw in a [previous +section](./data-types-and-runtime-rep.md#non-shared-data-types), there are some +types in Melange that compile to values that are not easy to manipulate from +JavaScript. To facilitate the communication from JavaScript code with values of +these types, Melange includes an attribute `deriving` that helps generating +conversion functions, as well as functions to create values from these types. In +particular, for variants and polymorphic variants. + +Additionally, `deriving` can be used with record types, to generate setters and +getters as well as creation functions. + +### Variants + +#### Creating values + +Use `@deriving accessors` on a variant type to create constructor values for +each branch. + +```ocaml +type action = + | Click + | Submit of string + | Cancel +[@@deriving accessors] +``` +```reasonml +[@deriving accessors] +type action = + | Click + | Submit(string) + | Cancel; +``` + +Melange will generate one `let` definition for each variant tag, implemented as +follows: + +- For variant tags with payloads, it will be a function that takes the payload + value as a parameter. +- For variant tags without payloads, it will be a constant with the runtime + value of the tag. + +Given the `action` type definition above, annotated with `deriving`, Melange +will generate something similar to the following code: + +```ocaml +type action = + | Click + | Submit of string + | Cancel + +let click = (Click : action) +let submit param = (Submit param : action) +let cancel = (Cancel : action) +``` +```reasonml +type action = + | Click + | Submit(string) + | Cancel; + +let click: action = Click; +let submit = (param): action => Submit(param); +let cancel: action = Cancel; +``` + +Which will result in the following JavaScript code after compilation: + +```javascript +function submit(param_0) { + return /* Submit */{ + _0: param_0 + }; +} + +var click = /* Click */0; + +var cancel = /* Cancel */1; +``` + +Note the generated definitions are lower-cased, and they can be safely used from +JavaScript code. For example, if the above JavaScript generated code was located +in a `generators.js` file, the definitions can be used like this: + +```javascript +const generators = require('./generators.js'); + +const hello = generators.submit("Hello"); +const click = generators.click; +``` + +#### Conversion functions + +Use `@deriving jsConverter` on a variant type to create converter functions that +allow to transform back and forth between JavaScript integers and Melange +variant values. + +There are a few differences with `@deriving accessors`: + +- `jsConverter` works with the `mel.as` attribute, while `accessors` does not +- `jsConverter` does not support variant tags with payload, while `accessors` + does +- `jsConverter` generates functions to transform values back and forth, while + `accessors` generates functions to create values + +Let’s see a version of the previous example, adapted to work with `jsConverter` +given the constraints above: + +```ocaml +type action = + | Click + | Submit [@mel.as 3] + | Cancel +[@@deriving jsConverter] +``` +```reasonml +[@deriving jsConverter] +type action = + | Click + | [@mel.as 3] Submit + | Cancel; +``` + +This will generate a couple of functions with the following types: + +```ocaml +val actionToJs : action -> int + +val actionFromJs : int -> action option +``` +```reasonml +external actionToJs: action => int = ; + +external actionFromJs: int => option(action) = ; +``` + +`actionToJs` returns integers from values of `action` type. It will start with 0 +for `Click`, 3 for `Submit` (because it was annotated with `mel.as`), and then 4 +for `Cancel`, in the same way that we described when [using `mel.int` with +polymorphic +variants](./working-with-js-objects-and-values.md#using-polymorphic-variants-to-bind-to-enums). + +`actionFromJs` returns a value of type `option`, because not every integer can +be converted into a variant tag of the `action` type. + +##### Hide runtime types + +For extra type safety, we can hide the runtime representation of variants +(`int`) from the generated functions, by using `jsConverter { newType }` payload +with `@deriving`: + +```ocaml +type action = + | Click + | Submit [@mel.as 3] + | Cancel +[@@deriving jsConverter { newType }] +``` +```reasonml +[@deriving jsConverter({newType: newType})] +type action = + | Click + | [@mel.as 3] Submit + | Cancel; +``` + +This feature relies on [abstract types](./language-concepts.md#abstract-types) +to hide the JavaScript runtime representation. It will generate functions with +the following types: + +```ocaml +val actionToJs : action -> abs_action + +val actionFromJs : abs_action -> action +``` +```reasonml +external actionToJs: action => abs_action = ; + +external actionFromJs: abs_action => action = ; +``` + +In the case of `actionFromJs`, the return value, unlike the previous case, is +not an option type. This is an example of "correct by construction": the only +way to create an `abs_action` is by calling the `actionToJs` function. + +### Polymorphic variants + +The `@deriving jsConverter` attribute is applicable to polymorphic variants as +well. + +> **_NOTE:_** Similarly to variants, the `@deriving jsConverter` attribute +> cannot be used when the polymorphic variant tags have payloads. Refer to the +> [section on runtime representation](./data-types-and-runtime-rep.md) to learn +> more about how polymorphic variants are represented in JavaScript. + +Let’s see an example: + +```ocaml +type action = + [ `Click + | `Submit [@mel.as "submit"] + | `Cancel + ] +[@@deriving jsConverter] +``` +```reasonml +[@deriving jsConverter] +type action = [ | `Click | [@mel.as "submit"] `Submit | `Cancel]; +``` + +Akin to the variant example, the following two functions will be generated: + +```ocaml +val actionToJs : action -> string + +val actionFromJs : string -> action option +``` +```reasonml +external actionToJs: action => string = ; + +external actionFromJs: string => option(action) = ; +``` + +The `jsConverter { newType }` payload can also be used with polymorphic +variants. + +### Records + +#### Accessing fields + +Use `@deriving accessors` on a record type to create accessor functions for its +record field names. + +```ocaml +type pet = { name : string } [@@deriving accessors] + +let pets = [| { name = "Brutus" }; { name = "Mochi" } |] + +let () = pets |. Belt.Array.map name |. Js.Array.join ~sep:"&" |. Js.log +``` +```reasonml +[@deriving accessors] +type pet = {name: string}; + +let pets = [|{name: "Brutus"}, {name: "Mochi"}|]; + +let () = pets->(Belt.Array.map(name))->(Js.Array.join(~sep="&"))->Js.log; +``` + +Melange will generate a function for each field defined in the record. In this +case, a function `name` that allows to get that field from any record of type +`pet`: + + +```ocaml +let name (param : pet) = param.name +``` +```reasonml +let name = (param: pet) => param.name; +``` + +Considering all the above, the produced JavaScript will be: + +```js +function name(param) { + return param.name; +} + +var pets = [ + { + name: "Brutus" + }, + { + name: "Mochi" + } +]; + +console.log(Belt_Array.map(pets, name).join("&")); +``` + +#### Generate JavaScript objects with optional properties + +In some occasions, we might want to emit a JavaScript object where some of the +keys can be conditionally present or absent. + +For instance, consider the following record: + +```ocaml +type person = { + name : string; + age : int option; +} +``` +```reasonml +type person = { + name: string, + age: option(int), +}; +``` + +An example of this use-case would be expecting `{ name = "John"; age = None }` +to generate a JavaScript object such as `{name: "Carl"}`, where the `age` key +doesn’t appear. + +The `@deriving jsProperties` attribute exists to solve this problem. When +present in a record type, `@deriving jsProperties` generates a constructor +function for creating values of the type, where the fields marked with +`[@mel.optional]` will be fully removed from the generated JavaScript object +when their value is `None`. + +Let’s see an example. Considering this Melange code: + +```ocaml +type person = { + name : string; + age : int option; [@mel.optional] +} +[@@deriving jsProperties] +``` +```reasonml +[@deriving jsProperties] +type person = { + name: string, + [@mel.optional] + age: option(int), +}; +``` + +Melange will generate a constructor to create values of this type. In our +example, the OCaml signature would look like this after preprocessing: + +```ocaml +type person + +val person : name:string -> ?age:int -> unit -> person +``` +```reasonml +type person; + +external person: (~name: string, ~age: int=?, unit) => person = ; +``` + +The `person` function can be used to create values of `person`. It is the only +possible way to create values of this type, since Melange makes it abstract. +Using literals like `{ name = "Alice"; age = None }` directly doesn’t type +check. + +Here is an example of how we can use it: + + +```ocaml +let alice = person ~name:"Alice" ~age:20 () +let bob = person ~name:"Bob" () +``` +```reasonml +let alice = person(~name="Alice", ~age=20, ()); +let bob = person(~name="Bob", ()); +``` + +This will generate the following JavaScript code. Note how there is no +JavaScript runtime overhead: + +```js +var alice = { + name: "Alice", + age: 20 +}; + +var bob = { + name: "Bob" +}; +``` + +The `person` function uses labeled arguments to represent record fields. Because +there is an optional argument `age`, it takes a last argument of type `unit`. +This non-labeled argument allows to omit the optional argument on function +application. Further details about optional labeled arguments can be found in +[the corresponding section of the OCaml +manual](https://v2.ocaml.org/manual/lablexamples.html#s:optional-arguments). + +#### Generating getters and setters + +In case we need both getters and setters for a record, we can use `deriving +getSet` to have them generated for free. + +If we take a record like this: + +```ocaml +type person = { + name : string; + age : int option; [@mel.optional] +} +[@@deriving getSet] +``` +```reasonml +[@deriving getSet] +type person = { + name: string, + [@mel.optional] + age: option(int), +}; +``` + +The `deriving` attribute can combine multiple derivers, for example we can +combine `jsProperties` with `getSet`: + +```ocaml +type person = { + name : string; + age : int option; [@mel.optional] +} +[@@deriving jsProperties, getSet] +``` +```reasonml +[@deriving (jsProperties, getSet)] +type person = { + name: string, + [@mel.optional] + age: option(int), +}; +``` + +When using `getSet`, Melange will create functions `nameGet` and `ageGet`, as +accessors for each record field. + + +```ocaml +let twenty = ageGet alice + +let bob = nameGet bob +``` +```reasonml +let twenty = ageGet(alice); + +let bob = nameGet(bob); +``` + +This generates: + +```javascript +var twenty = alice.age; + +var bob = bob.name; +``` + +The functions are named by appending `Get` to the field names of the record to +prevent potential clashes with other values within the module. If shorter names +are preferred for the getter functions, there is an alternate getSet { light }getSet({light: light}) payload that can be passed +to `deriving`: + +```ocaml +type person = { + name : string; + age : int; +} +[@@deriving jsProperties, getSet { light }] + +let alice = person ~name:"Alice" ~age:20 +let aliceName = name alice +``` +```reasonml +[@deriving (jsProperties, getSet({light: light}))] +type person = { + name: string, + age: int, +}; + +let alice = person(~name="Alice", ~age=20); +let aliceName = name(alice); +``` + +Which generates: + +```javascript +var alice = { + name: "Alice", + age: 20 +}; + +var aliceName = alice.name; +``` + +In this example, the getter functions share the same names as the object fields. +Another distinction from the previous example is that the `person` constructor +function no longer requires the final `unit` argument since we have excluded the +optional field in this case. + +> **_NOTE:_** The `mel.as` attribute can still be applied to record fields when +> the record type is annotated with `deriving`, allowing for the renaming of +> fields in the resulting JavaScript objects, as demonstrated in the section +> about [binding to objects with static +> shape](./working-with-js-objects-and-values.md#objects-with-static-shape-record-like). +> However, the option to pass indices to the `mel.as` decorator (like `[@mel.as +> "0"]`) to change the runtime representation to an array is not available when +> using `deriving`. + +##### Compatibility with OCaml features + +The `@deriving getSet` attribute and its lightweight variant can be used with +[mutable +fields](https://v2.ocaml.org/manual/coreexamples.html#s:imperative-features) and +[private types](https://v2.ocaml.org/manual/privatetypes.html), which are +features inherited by Melange from OCaml. + +When the record type has mutable fields, Melange will generate setter functions +for them. For example: + +```ocaml +type person = { + name : string; + mutable age : int; +} +[@@deriving getSet] + +let alice = person ~name:"Alice" ~age:20 + +let () = ageSet alice 21 +``` +```reasonml +[@deriving getSet] +type person = { + name: string, + mutable age: int, +}; + +let alice = person(~name="Alice", ~age=20); + +let () = ageSet(alice, 21); +``` + +This will generate: + +```javascript +var alice = { + name: "Alice", + age: 20 +}; + +alice.age = 21; +``` + +If the `mutable` keyword is omitted from the interface file, Melange will not +include the setter function in the module signature, preventing other modules +from mutating any values from the type. + +Private types can be used to prevent Melange from creating the constructor +function. For example, if we define `person` type as private: + +```ocaml +type person = private { + name : string; + age : int; +} +[@@deriving getSet] +``` +```reasonml +[@deriving getSet] +type person = + pri { + name: string, + age: int, + }; +``` + +The accessors `nameGet` and `ageGet` will still be generated, but not the +constructor `person`. This is useful when binding to JavaScript objects while +preventing any Melange code from creating values of such type. + +## Use Melange code from JavaScript + +As mentioned in the [build system +section](./build-system.md#commonjs-or-es6-modules), Melange allows to produce +both CommonJS and ES6 modules. In both cases, using Melange-generated JavaScript +code from any hand-written JavaScript file works as expected. + +The following definition: + +```ocaml +let print name = "Hello" ^ name +``` +```reasonml +let print = name => "Hello" ++ name; +``` + +Will generate this JavaScript code, when using CommonJS (the default): + +```js +function print(name) { + return "Hello" + name; +} + +exports.print = print; +``` + +When using ES6 (through the `(module_systems es6)` field in `melange.emit`) this +code will be generated: + +```js +function print(name) { + return "Hello" + name; +} + +export { + print , +} +``` + +So one can use either `require` or `import` (depending on the module system of +choice) to import the `print` value in a JavaScript file. + +### Default ES6 values + +One special case occur when working with JavaScript imports in ES6 modules that +look like this: + +```js +import ten from 'numbers.js'; +``` + +This import expects `numbers.js` to have a default export, like: + +```js +export default ten = 10; +``` + +To emulate this kind of exports from Melange, one just needs to define a +`default` value. + +For example, in a file named numbers.mlnumbers.re: + +```ocaml +let default = 10 +``` +```reasonml +let default = 10; +``` + +That way, Melange will set the value on the `default` export so it can be +consumed as default import on the JavaScript side. diff --git a/docs/attributes-and-extension-nodes.md b/docs/attributes-and-extension-nodes.md new file mode 100644 index 000000000..a4e73955b --- /dev/null +++ b/docs/attributes-and-extension-nodes.md @@ -0,0 +1,305 @@ +# Melange attributes and extension nodes + +## Attributes + +These attributes are used to annotate `external` definitions: + +- [`mel.get`](./working-with-js-objects-and-values.md#bind-to-object-properties): + read JavaScript object properties statically by name, using the dot notation + `.` +- [`mel.get_index`](./working-with-js-objects-and-values.md#bind-to-object-properties): + read a JavaScript object’s properties dynamically by using the bracket + notation `[]` +- [`mel.module`](./working-with-js-objects-and-values.md#using-functions-from-other-javascript-modules): + bind to a value from a JavaScript module +- [`mel.new`](./working-with-js-objects-and-values.md#javascript-classes): bind + to a JavaScript class constructor +- [`mel.obj`](./working-with-js-objects-and-values.md#using-external-functions): + create a JavaScript object +- [`mel.return`](./working-with-js-objects-and-values.md#wrapping-returned-nullable-values): + automate conversion from nullable values to `Option.t` values +- [`mel.send`](./working-with-js-objects-and-values.md#calling-an-object-method): + call a JavaScript object method using [pipe + first](./language-concepts.md#pipe-first) convention +- [`mel.send.pipe`](./working-with-js-objects-and-values.md#calling-an-object-method): + call a JavaScript object method using [pipe + last](./language-concepts.md#pipe-last) convention +- [`mel.set`](./working-with-js-objects-and-values.md#bind-to-object-properties): + set JavaScript object properties statically by name, using the dot notation + `.` +- [`mel.set_index`](./working-with-js-objects-and-values.md#bind-to-object-properties): + set JavaScript object properties dynamically by using the bracket notation + `[]` +- [`mel.scope`](./working-with-js-objects-and-values.md#binding-to-properties-inside-a-module-or-global): + reach to deeper properties inside a JavaScript object +- [`mel.splice`](./working-with-js-objects-and-values.md#variadic-function-arguments): + a deprecated attribute, is an alternate form of `mel.variadic` +- [`mel.variadic`](./working-with-js-objects-and-values.md#variadic-function-arguments): + bind to a function taking variadic arguments from an array + +These attributes are used to annotate arguments in `external` definitions: + +- [`u`](./working-with-js-objects-and-values.md#binding-to-callbacks): define + function arguments as uncurried (manual) +- [`mel.int`](./working-with-js-objects-and-values.md#using-polymorphic-variants-to-bind-to-enums): + compile function argument to an int +- [`mel.string`](./working-with-js-objects-and-values.md#using-polymorphic-variants-to-bind-to-enums): + compile function argument to a string +- [`mel.this`](./working-with-js-objects-and-values.md#modeling-this-based-callbacks): + bind to `this` based callbacks +- [`mel.uncurry`](./working-with-js-objects-and-values.md#binding-to-callbacks): + define function arguments as uncurried (automated) +- [`mel.unwrap`](./working-with-js-objects-and-values.md#approach-2-polymorphic-variant-mel-unwrap): + unwrap variant values + +These attributes are used in places like records, fields, arguments, functions, +and more: + +- `mel.as`: redefine the name generated in the JavaScript output code. Used in + [constant function + arguments](./working-with-js-objects-and-values.md#constant-values-as-arguments), + [variants](./advanced-js-interop.md#conversion-functions), polymorphic + variants (either [inlined in external + functions](./working-with-js-objects-and-values.md#using-polymorphic-variants-to-bind-to-enums) + or [in type definitions](./advanced-js-interop.md#polymorphic-variants)) and + [record + fields](./working-with-js-objects-and-values.md#objects-with-static-shape-record-like). +- [`deriving`](./advanced-js-interop.md#generate-getters-setters-and-constructors): + generate getters and setters for some types +- [`mel.inline`](#inlining-constant-values): forcefully inline constant values +- [`optional`](./advanced-js-interop.md#generate-javascript-objects-with-optional-properties): + translates optional fields in a record to omitted properties in the generated + JavaScript object (combines with `deriving`) + +## Extension nodes + +In order to use any of these extension nodes, you will have to add the melange +PPX preprocessor to your project. To do so, add the following to the `dune` +file: + +```dune +(library + (name lib) + (modes melange) + (preprocess + (pps melange.ppx))) +``` + +The same field `preprocess` can be added to `melange.emit`. + +Here is the list of all the extension nodes supported by Melange: + +- [`mel.debugger`](#debugger): insert `debugger` statements +- [`mel.external`](#detect-global-variables): read global values +- [`mel.obj`](./working-with-js-objects-and-values.md#using-js-t-objects): + create JavaScript object literals +- [`mel.raw`](#generate-raw-javascript): write raw JavaScript code +- [`mel.re`](./data-types-and-runtime-rep.md#regular-expressions): insert + regular expressions + +## Generate raw JavaScript + +It is possible to directly write JavaScript code from a Melange file. This is +unsafe, but it can be useful for prototyping or as an escape hatch. + +To do it, we will use the `mel.raw` +[extension](https://v2.ocaml.org/manual/extensionnodes.html): + +```ocaml +let add = [%mel.raw {| + function(a, b) { + console.log("hello from raw JavaScript!"); + return a + b; + } +|}] + +let () = Js.log (add 1 2) +``` +```reasonml +let add = [%mel.raw + {| + function(a, b) { + console.log("hello from raw JavaScript!"); + return a + b; + } +|} +]; + +let () = Js.log(add(1, 2)); +``` + +The `{||}` strings are called ["quoted +strings"](https://ocaml.org/manual/lex.html#quoted-string-id). They are similar +to JavaScript’s template literals, in the sense that they are multi-line, and +there is no need to escape characters inside the string. + +Using one percentage signthe extension name between square brackets +(`[%mel.raw ]`) is useful to define expressions (function bodies, or +other values) where the implementation is directly JavaScript. This is useful as +we can attach the type signature already in the same line, to make our code +safer. For example: + +```ocaml +let f : unit -> int = [%mel.raw "function() {return 1}"] +``` +```reasonml +let f: unit => int = ([%mel.raw "function() {return 1}"]: unit => int); +``` + +Using two percentage signs (`[%%mel.raw +"xxx"]`)the extension name without square +brackets (`%mel.raw "xxx"`) is reserved for definitions in a +[structure](https://v2.ocaml.org/manual/moduleexamples.html#s:module:structures) +or [signature](https://v2.ocaml.org/manual/moduleexamples.html#s%3Asignature). + +For example: + +```ocaml +[%%mel.raw "var a = 1"] +``` +```reasonml +[%%mel.raw "var a = 1"]; +``` + +## Debugger + +Melange allows you to inject a `debugger;` expression using the `mel.debugger` +extension: + +```ocaml +let f x y = + [%mel.debugger]; + x + y +``` +```reasonml +let f = (x, y) => { + [%mel.debugger]; + x + y; +}; +``` + +Output: + +```javascript +function f (x,y) { + debugger; // JavaScript developer tools will set a breakpoint and stop here + return x + y | 0; +} +``` + +## Detect global variables + +Melange provides a relatively type safe approach to use globals that might be +defined either in the JavaScript runtime environment: `mel.external`. + +`[%mel.external id]` will check if the JavaScript value `id` is `undefined` or +not, and return an `Option.t` value accordingly. + +For example: + +```ocaml +let () = match [%mel.external __DEV__] with +| Some _ -> Js.log "dev mode" +| None -> Js.log "production mode" +``` +```reasonml +let () = + switch ([%mel.external __DEV__]) { + | Some(_) => Js.log("dev mode") + | None => Js.log("production mode") + }; +``` + +Another example: + +```ocaml +let () = match [%mel.external __filename] with +| Some f -> Js.log f +| None -> Js.log "non-node environment" +``` +```reasonml +let () = + switch ([%mel.external __filename]) { + | Some(f) => Js.log(f) + | None => Js.log("non-node environment") + }; +``` + +`[%mel.external id]` makes `id` available as a value of type 'a Option.tOption.t('a), meaning its wrapped value is +compatible with any type. If you use the value, it is recommended to annotate it +into a known type first to avoid runtime issues. + +## Inlining constant values + +Some JavaScript idioms require special constants to be inlined since they serve +as de-facto directives for bundlers. A common example is `process.env.NODE_ENV`: + +```js +if (process.env.NODE_ENV !== "production") { + // Development-only code +} +``` + +becomes: + +```js +if ("development" !== "production") { + // Development-only code +} +``` + +In this case, bundlers such as Webpack can tell that the `if` statement always +evaluates to a specific branch and eliminate the others. + +Melange provides the `mel.inline` attribute to achieve the same goal in +generated JavaScript. Let’s look at an example: + +```ocaml +external node_env : string = "NODE_ENV" [@@mel.scope "process", "env"] + +let development = "development" +let () = if node_env <> development then Js.log "Only in Production" + +let development_inline = "development" [@@mel.inline] +let () = if node_env <> development_inline then Js.log "Only in Production" +``` +```reasonml +[@mel.scope ("process", "env")] external node_env: string = "NODE_ENV"; + +let development = "development"; +let () = + if (node_env != development) { + Js.log("Only in Production"); + }; + +[@mel.inline] +let development_inline = "development"; +let () = + if (node_env != development_inline) { + Js.log("Only in Production"); + }; +``` + +As we can see in the generated JavaScript presented below: + +- the `development` variable is emitted + - it gets used as a variable `process.env.NODE_ENV !== development` in the + `if` statement +- the `development_inline` variable isn’t present in the final output + - its value is inlined in the `if` statement: `process.env.NODE_ENV !== + "development"` + +```js +var development = "development"; + +if (process.env.NODE_ENV !== development) { + console.log("Only in Production"); +} + +if (process.env.NODE_ENV !== "development") { + console.log("Only in Production"); +} +``` diff --git a/docs/bindings-cookbook.md b/docs/bindings-cookbook.md new file mode 100644 index 000000000..0a1ac4798 --- /dev/null +++ b/docs/bindings-cookbook.md @@ -0,0 +1,633 @@ +# Bindings cookbook + +## Globals + +### `window`: global variable + +```ocaml +external window : Dom.window = "window" +``` +```reasonml +external window: Dom.window = "window"; +``` + +See the [Using global functions or +values](./working-with-js-objects-and-values.md#using-global-functions-or-values) +section for more information. + +### `window?`: does global variable exist + +```ocaml +let _ = match [%mel.external window] with +| Some _ -> "window exists" +| None -> "window does not exist" +``` +```reasonml +let _ = + switch ([%mel.external window]) { + | Some(_) => "window exists" + | None => "window does not exist" + }; +``` + +See the [Detect global +variables](./attributes-and-extension-nodes.md#detect-global-variables) section +for more information. + +### `Math.PI`: variable in global module + +```ocaml +external pi : float = "PI" [@@mel.scope "Math"] +``` +```reasonml +[@mel.scope "Math"] external pi: float = "PI"; +``` + +See the [Binding to properties inside a module or +global](./working-with-js-objects-and-values.md#binding-to-properties-inside-a-module-or-global) +section for more information. + +### `console.log`: function in global module + +```ocaml +external log : 'a -> unit = "log" [@@mel.scope "console"] +``` +```reasonml +[@mel.scope "console"] external log: 'a => unit = "log"; +``` + +See the [Binding to properties inside a module or +global](./working-with-js-objects-and-values.md#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 + +```ocaml +external join : string -> string -> string = "join" [@@mel.module "path"] +let dir = join "a" "b" +``` +```reasonml +[@mel.module "path"] external join: (string, string) => string = "join"; +let dir = join("a", "b"); +``` + +See the [Using functions from other JavaScript +modules](./working-with-js-objects-and-values.md#using-functions-from-other-javascript-modules) +section for more information. + +### `const foo = require('foo'); foo(1)`: import entire module as a value + +```ocaml +external foo : int -> unit = "foo" [@@mel.module] +let () = foo 1 +``` +```reasonml +[@mel.module] external foo: int => unit = "foo"; +let () = foo(1); +``` + +See the [Using functions from other JavaScript +modules](./working-with-js-objects-and-values.md#using-functions-from-other-javascript-modules) +section for more information. + +### `import foo from 'foo'; foo(1)`: import ES6 module default export + +```ocaml +external foo : int -> unit = "default" [@@mel.module "foo"] +let () = foo 1 +``` +```reasonml +[@mel.module "foo"] external foo: int => unit = "default"; +let () = foo(1); +``` + +See the [Using functions from other JavaScript +modules](./working-with-js-objects-and-values.md#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 + +```ocaml +module Foo = struct + module Bar = struct + external baz : unit -> unit = "baz" [@@mel.module "foo"] [@@mel.scope "bar"] + end +end + +let () = Foo.Bar.baz () +``` +```reasonml +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](./working-with-js-objects-and-values.md#binding-to-properties-inside-a-module-or-global) +section for more information. + +## Functions + +### `const dir = path.join('a', 'b', ...)`: function with rest args + +```ocaml +external join : string array -> string = "join" [@@mel.module "path"] [@@mel.variadic] +let dir = join [| "a"; "b" |] +``` +```reasonml +[@mel.module "path"] [@mel.variadic] +external join: array(string) => string = "join"; +let dir = join([|"a", "b"|]); +``` + +See the [Variadic function +arguments](./working-with-js-objects-and-values.md#variadic-function-arguments) +section for more information. + +### `const nums = range(start, stop, step)`: call a function with named arguments for readability + +```ocaml +external range : start:int -> stop:int -> step:int -> int array = "range" +let nums = range ~start:1 ~stop:10 ~step:2 +``` +```reasonml +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 + +```ocaml +external fooString : string -> unit = "foo" +external fooBool : bool -> unit = "foo" + +let () = fooString "" +let () = fooBool true +``` +```reasonml +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) + +```ocaml +external range : start:int -> stop:int -> ?step:int -> unit -> int array + = "range" + +let nums = range ~start:1 ~stop:10 () +``` +```reasonml +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 + +```ocaml +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 ()) () +``` +```reasonml +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](./working-with-js-objects-and-values.md#using-external-functions) +section for more information. + +### `forEach(start, stop, item => console.log(item))`: model a callback + +```ocaml +external forEach : + start:int -> stop:int -> ((int -> unit)[@mel.uncurry]) -> unit = "forEach" + +let () = forEach ~start:1 ~stop:10 Js.log +``` +```reasonml +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](./working-with-js-objects-and-values.md#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: + +```ocaml +let person = [%mel.obj { id = 1; name = "Alice" }] +``` +```reasonml +let person = {"id": 1, "name": "Alice"}; +``` + +See the [Using `Js.t` +objects](./working-with-js-objects-and-values.md#using-js-t-objects) section for +more information. + +Alternatively, for greater type accuracy, one can create a record type and a +value: + +```ocaml +type person = { id : int; name : string } +let person = { id = 1; name = "Alice" } +``` +```reasonml +type person = { + id: int, + name: string, +}; +let person = {id: 1, name: "Alice"}; +``` + +See the [Using OCaml +records](./working-with-js-objects-and-values.md#using-ocaml-records) section +for more information. + +### `person.name`: get a prop + + +```ocaml +let name = person##name +``` +```reasonml +let name = person##name; +``` + +Alternatively, if `person` value is of record type as mentioned in the section +above: + + +```ocaml +let name = person.name +``` +```reasonml +let name = person.name; +``` + +### `person.id = 0`: set a prop + + +```ocaml +external set_id : person -> int -> unit = "id" [@@mel.set] + +let () = set_id person 0 +``` +```reasonml +[@mel.set] external set_id: (person, int) => unit = "id"; + +let () = set_id(person, 0); +``` + +### `const {id, name} = person`: object with destructuring + +```ocaml +type person = { id : int; name : string } + +let person = { id = 1; name = "Alice" } +let { id; name } = person +``` +```reasonml +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 + +```javascript +const foo = new Foo(); +foo.bar(); +``` + +You will write: + + +```ocaml +let foo = Foo.make () +let () = Foo.bar foo +``` +```reasonml +let foo = Foo.make(); +let () = Foo.bar(foo); +``` + +Note that many of the techniques shown in the [Functions](#functions) section +are applicable to the instance members shown below. + +### `const foo = new Foo()`: call a class constructor + +```ocaml +module Foo = struct + type t + external make : unit -> t = "Foo" [@@mel.new] +end + +let foo = Foo.make () +``` +```reasonml +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](./language-concepts.md#abstract-types) 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](./working-with-js-objects-and-values.md#javascript-classes) section for +more information. + +### `const bar = foo.bar`: get an instance property + +```ocaml +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 +``` +```reasonml +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](./working-with-js-objects-and-values.md#bind-to-object-properties) +section for more information. + +### `foo.bar = 1`: set an instance property + +```ocaml +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 +``` +```reasonml +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 + +```ocaml +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 +``` +```reasonml +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](./working-with-js-objects-and-values.md#calling-an-object-method) +section for more information. + +### `const newStr = str.replace(substr, newSubstr)`: non-mutating instance method + +```ocaml +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 +``` +```reasonml +[@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](./working-with-js-objects-and-values.md#calling-an-object-method) +section for more information. + +### `arr.sort(compareFunction)`: mutating instance method + + +```ocaml +external sort : 'a array -> (('a -> 'a -> int)[@mel.uncurry]) -> 'a array + = "sort" +[@@mel.send] + +let _ = sort arr compare +``` +```reasonml +[@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 + +```ocaml +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 +``` +```reasonml +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](./data-types-and-runtime-rep.md#non-shared-data-types) section for more +information. + +### `foo.bar == null`: check for null or undefined + +```ocaml +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 +``` +```reasonml +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](./working-with-js-objects-and-values.md#wrapping-returned-nullable-values) +section for more information. diff --git a/docs/communicate-with-javascript.md b/docs/communicate-with-javascript.md index 477bf2330..29d0c23b8 100644 --- a/docs/communicate-with-javascript.md +++ b/docs/communicate-with-javascript.md @@ -1,3874 +1,38 @@ # Communicate with JavaScript Melange interoperates very well with JavaScript, and provides a wide array of -features to communicate with foreign JavaScript code. To learn about these -techniques (generically known as "bindings"), we will first go through the -language concepts that they build upon, then we will see how types in Melange -map to JavaScript runtime types. Finally, we will provide a variety of use cases -with examples to show how to communicate to and from JavaScript. - -If you already have in mind some JavaScript that you want to write, check the -last section ["Bindings cookbook"](#bindings-cookbook) to see how the same code -can be written with Melange. - -## Language concepts - -The concepts covered in the following sections are a small subset of the OCaml -language. However, they are essential for understanding how to communicate with -JavaScript, and the features that Melange exposes to do so. - -### Attributes and extension nodes - -In order to interact with JavaScript, Melange needs to extend the language to -provide blocks that express these interactions. - -One approach could be to introduce new syntactic constructs (keywords and such) -to do so, for example: - -```text -javascript add : int -> int -> int = {|function(x,y){ - return x + y -}|} -``` - -But this would break compatibility with OCaml, which is one of the main goals of -Melange. - -Fortunately, OCaml provides mechanisms to extend its language without breaking -compatibility with the parser or the language. These mechanisms are composed by -two parts: - -- First, some syntax additions to define parts of the code that will be extended - or replaced -- Second, compile-time OCaml native programs called [PPX - rewriters](https://ocaml.org/docs/metaprogramming), that will read the syntax - additions defined above and proceed to extend or replace them - -The syntax additions come in two flavors, called [extension -nodes](https://v2.ocaml.org/manual/extensionnodes.html) and -[attributes](https://v2.ocaml.org/manual/attributes.html). - -#### Extension nodes - -Extension nodes are blocks that are supposed to be replaced by a specific type -of PPX rewriters called extenders. Extension nodes use the `%` character to be -identified. Extenders will take the extension node and replace it with a valid -OCaml AST (abstract syntax tree). - -An example where Melange uses extension nodes to communicate with JavaScript is -to produce "raw" JavaScript inside a Melange program: - -```ocaml -[%%mel.raw "var a = 1; var b = 2"] -let add = [%mel.raw "a + b"] -``` -```reasonml -[%%mel.raw "var a = 1; var b = 2"]; -let add = [%mel.raw "a + b"]; -``` - -Which will generate the following JavaScript code: - -```js -var a = 1; var b = 2 -var add = a + b -``` - -The difference between one and two percentage characters is detailed in the -[OCaml documentation](https://v2.ocaml.org/manual/extensionnodes.html). - -#### Attributes - -Attributes are "decorations" applied to specific parts of the code to provide -additional information. In Melange, attributes are used in two ways to enhance -the expressiveness of generating JavaScript code: either reusing existing OCaml -built-in attributes or defining new ones. - -##### Reusing OCaml attributes - -The first approach is leveraging the existing [OCaml’s built-in -attributes](https://v2.ocaml.org/manual/attributes.html#ss:builtin-attributes) -to be used for JavaScript generation. - -One prominent example of OCaml attributes that can be used in Melange projects -is the `unboxed` attribute, which optimizes the compilation of single-field -records and variants with a single tag to their raw values. This is useful when -defining type aliases that we don’t want to mix up, or when binding to -JavaScript code that uses heterogeneous collections. An example of the latter is -discussed in the [variadic function arguments](#variadic-function-arguments) -section. - -For instance: - -```ocaml -type name = - | Name of string [@@unboxed] -let student_name = Name "alice" -``` -```reasonml -[@unboxed] -type name = - | Name(string); -let student_name = Name("alice"); -``` - -Compiles into: - -```js -var student_name = "alice"; -``` - -Other OCaml pre-built attributes like `alert` or `deprecated` can be used with -Melange as well. - -##### Defining new attributes - -The second approach is introducing new attributes specifically designed for -Melange, such as the [`mel.set` attribute](#bind-to-object-properties) used to -bind to properties of JavaScript objects. The complete list of attributes -introduced by Melange can be found -[here](#list-of-attributes-and-extension-nodes). - -Attribute annotations can use one, two or three `@` characters depending on -their placement in the code and which kind of syntax tree node they are -annotating. More information about attributes can be found in the [dedicated -OCaml manual page](https://v2.ocaml.org/manual/attributes.html). - -Here are some samples using Melange attributes -[`mel.set`](#bind-to-object-properties) and [`mel.as`](#using-ocaml-records): - -```ocaml -type document -external setTitleDom : document -> string -> unit = "title" [@@mel.set] - -type t = { - age : int; [@mel.as "a"] - name : string; [@mel.as "n"] -} -``` -```reasonml -type document; -[@mel.set] external setTitleDom: (document, string) => unit = "title"; - -type t = { - [@mel.as "a"] - age: int, - [@mel.as "n"] - name: string, -}; -``` - -To learn more about preprocessors, attributes and extension nodes, check the -[section about PPX -rewriters](https://ocaml.org/docs/metaprogramming#ppx-rewriters) in the OCaml -docs. - -### External functions - -Most of the system that Melange exposes to communicate with JavaScript is built -on top of an OCaml language construct called `external`. - -`external` is a keyword for declaring a value in OCaml that will [interface with -C code](https://v2.ocaml.org/manual/intfc.html): - -```ocaml -external my_c_function : int -> string = "someCFunctionName" -``` -```reasonml -external my_c_function: int => string = "someCFunctionName"; -``` - -It is like a `let` binding, except that the body of an external is a string. -That string has a specific meaning depending on the context. For native OCaml, -it usually refers to a C function with that name. For Melange, it refers to the -functions or values that exist in the runtime JavaScript code, and will be used -from Melange. - -In Melange, externals can be used to [bind to global JavaScript -objects](#using-global-functions-or-values). They can also be decorated with -certain `[@mel.xxx]` attributes to facilitate the creation of bindings in -specific scenarios. Each one of the [available -attributes](#list-of-attributes-and-extension-nodes) will be further explained -in the next sections. - -Once declared, one can use an `external` as a normal value. Melange external -functions are turned into the expected JavaScript values, inlined into their -callers during compilation, and completely erased afterwards. They don’t appear -in the JavaScript output, so there are no costs on bundling size. - -**Note**: it is recommended to use external functions and the `[@mel.xxx]` -attributes in the interface files as well, as this allows some optimizations -where the resulting JavaScript values can be directly inlined at the call sites. - -#### Special identity external - -One external worth mentioning is the following one: - -```ocaml -type foo = string -type bar = int -external danger_zone : foo -> bar = "%identity" -``` -```reasonml -type foo = string; -type bar = int; -external danger_zone: foo => bar = "%identity"; -``` - -This is a final escape hatch which does nothing but convert from the type `foo` -to `bar`. In the following sections, if you ever fail to write an `external`, -you can fall back to using this one. But try not to. - -### Abstract types - -In the subsequent sections, you will come across examples of bindings where a -type is defined without being assigned to a value. Here is an example: - -```ocaml -type document -``` -```reasonml -type document; -``` - -These types are referred to as "abstract types" and are commonly used together -with external functions that define operations over values when communicating -with JavaScript. - -Abstract types enable defining types for specific values originating from -JavaScript while omitting unnecessary details. An illustration is the `document` -type mentioned earlier, which has several -[properties](https://developer.mozilla.org/en-US/docs/Web/API/Document). By -using abstract types, one can focus solely on the required aspects of the -`document` value that the Melange program requires, rather than defining all its -properties. Consider the following example: - -```ocaml -type document - -external document : document = "document" -external set_title : document -> string -> unit = "title" [@@mel.set] -``` -```reasonml -type document; - -external document: document = "document"; -[@mel.set] external set_title: (document, string) => unit = "title"; -``` - -Subsequent sections delve into the details about the -[`mel.set`](#bind-to-object-properties) attribute and [how to bind to global -values](#using-global-functions-or-values) like `document`. - -For a comprehensive understanding of abstract types and their usefulness, refer -to the "Encapsulation" section of the [OCaml Cornell -textbook](https://cs3110.github.io/textbook/chapters/modules/encapsulation.html). - -### Pipe operators - -There are two pipe operators available in Melange: - -- A _pipe last_ operator `|>`, available [in - OCaml](https://v2.ocaml.org/api/Stdlib.html#1_Compositionoperators) and - inherited in Melange -- A _pipe first_ operator \|.\-\>, available exclusively in Melange - -Let’s see the differences between the two. - -#### Pipe last - -Since version 4.01, OCaml includes a reverse application or "pipe" (`|>`) -operator, an infix operator that applies the result from the previous expression -the next function. As a backend for OCaml, Melange inherits this operator. - -The pipe operator could be implemented like this (the real implementation is a -bit -[different](https://github.com/ocaml/ocaml/blob/d9547617e8b14119beacafaa2546cbebfac1bfe5/stdlib/stdlib.ml#L48)): - -```ocaml -let ( |> ) f g = g f -``` -```reasonml -let (|>) = (f, g) => g(f); -``` - -This operator is useful when multiple functions are applied to some value in -sequence, with the output of each function becoming the input of the next (a -_pipeline_). - -For example, assuming we have a function `square` defined as: - -```ocaml -let square x = x * x -``` -```reasonml -let square = x => x * x; -``` - -We are using it like: - -```ocaml -let ten = succ (square 3) -``` -```reasonml -let ten = succ(square(3)); -``` - -The pipe operator allows to write the computation for `ten` in left-to-right -order, as [it has left -associativity](https://v2.ocaml.org/manual/expr.html#ss:precedence-and-associativity): - -```ocaml -let ten = 3 |> square |> succ -``` -```reasonml -let ten = 3 |> square |> succ; -``` - -When working with functions that can take multiple arguments, the pipe operator -works best when the functions take the data we are processing as the last -argument. For example: - -```ocaml -let sum = List.fold_left ( + ) 0 - -let sum_sq = - [ 1; 2; 3 ] - |> List.map square (* [1; 4; 9] *) - |> sum (* 1 + 4 + 9 *) -``` -```reasonml -let sum = List.fold_left((+), 0); - -let sum_sq = - [1, 2, 3] - |> List.map(square) /* [1; 4; 9] */ - |> sum; /* 1 + 4 + 9 */ -``` - -The above example can be written concisely because the `List.map` function in -the [OCaml standard library](https://v2.ocaml.org/api/Stdlib.List.html) takes -the list as the second argument. This convention is sometimes referred to as -"data-last", and is widely adopted in the OCaml ecosystem. Data-last and the -pipe operator `|>` work great with currying, so they are a great fit for the -language. - -However, there are some limitations when using data-last when it comes to error -handling. In the given example, if we mistakenly used the wrong function: - -```ocaml -let sum_sq = - [ 1; 2; 3 ] - |> List.map String.cat - |> sum -``` -```reasonml -let sum_sq = [1, 2, 3] |> List.map(String.cat) |> sum; -``` - -The compiler would rightfully raise an error: - -
-
4 |   [ 1; 2; 3 ]
-          ^
-  Error: This expression has type int but an expression was expected of type
-          string
-
1 |   [ 1, 2, 3 ]
-          ^
-  Error: This expression has type int but an expression was expected of type
-          string
-
- -Note that instead of telling us that we are passing the wrong function in -`List.map` (`String.cat`), the error points to the list itself. This behavior -aligns with the way type inference works, as the compiler infers types from left -to right. Since `[ 1; 2; 3 ] |> List.map String.cat` is equivalent to `List.map -String.cat [ 1; 2; 3 ]`, the type mismatch is detected when the list is type -checked, after `String.cat` has been processed. - -With the goal of addressing this kind of limitations, Melange introduces the -pipe first operator \|.\-\>. - -#### Pipe first - -To overcome the constraints mentioned above, Melange introduces the pipe first -operator \|.\-\>. - -As its name suggests, the pipe first operator is better suited for functions -where the data is passed as the first argument. - -The functions in the Belt libraryBelt library included with Melange -have been designed with the data-first convention in mind, so they work best -with the pipe first operator. - -For example, we can rewrite the example above using `Belt.List.map` and the pipe -first operator: - -```ocaml -let sum_sq = - [ 1; 2; 3 ] - |. Belt.List.map square - |. sum -``` -```reasonml -let sum_sq = [1, 2, 3]->(Belt.List.map(square))->sum; -``` - -We can see the difference on the error we get if the wrong function is passed to -`Belt.List.map`: - -```ocaml -let sum_sq = - [ 1; 2; 3 ] - |. Belt.List.map String.cat - |. sum -``` -```reasonml -let sum_sq = [1, 2, 3]->(Belt.List.map(String.cat))->sum; -``` - -The compiler will show this error message: - -
-
4 |   |. Belt.List.map String.cat
-                       ^^^^^^^^^^
-Error: This expression has type string -> string -> string
-       but an expression was expected of type int -> 'a
-       Type string is not compatible with type int
-
2 | let sum_sq = [1, 2, 3]->(Belt.List.map(String.cat))->sum;
-                                           ^^^^^^^^^^
-Error: This expression has type string -> string -> string
-       but an expression was expected of type int -> 'a
-       Type string is not compatible with type int
-
- -The error points now to the function passed to `Belt.List.map`, which is more -natural with the way the code is being written. - -Melange supports writing bindings to JavaScript using any of the two -conventions, data-first or data-last, as shown in the ["Chaining" -section](#chaining). - -For further details about the differences between the two operators, the -data-first and data-last conventions and the trade-offs between them, one can -refer to [this related blog -post](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/). - -## 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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) | -| Option.t `None` | `undefined` | -| 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}` | -| 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: - -```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](#shared-types), which have a transparent representation as JavaScript -values, and then go through the [non-shared types](#non-shared-data-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](https://v2.ocaml.org/manual/lex.html#sss:stringliterals) 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](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) -with a 53-bit mantissa and exponents from -1022 to 1023. This happens to be the -same encoding as [JavaScript -numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding), -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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#fixed-width_number_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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding), -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](https://github.com/reasonml/reason-react/), the Melange bindings -for [React](https://react.dev/). 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](#bind-to-js-object). - -#### 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](#generate-getters-setters-and-constructors) 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 - -## List of attributes and extension nodes - -**Attributes:** - -These attributes are used to annotate `external` definitions: - -- [`mel.get`](#bind-to-object-properties): read JavaScript object properties - statically by name, using the dot notation `.` -- [`mel.get_index`](#bind-to-object-properties): read a JavaScript object’s - properties dynamically by using the bracket notation `[]` -- [`mel.module`](#using-functions-from-other-javascript-modules): bind to a - value from a JavaScript module -- [`mel.new`](#javascript-classes): bind to a JavaScript class constructor -- [`mel.obj`](#using-external-functions): create a JavaScript object -- [`mel.return`](#wrapping-returned-nullable-values): automate conversion from - nullable values to `Option.t` values -- [`mel.send`](#calling-an-object-method): call a JavaScript object method using - [pipe first](#pipe-first) convention -- [`mel.send.pipe`](#calling-an-object-method): call a JavaScript object method - using [pipe last](#pipe-last) convention -- [`mel.set`](#bind-to-object-properties): set JavaScript object properties - statically by name, using the dot notation `.` -- [`mel.set_index`](#bind-to-object-properties): set JavaScript object - properties dynamically by using the bracket notation `[]` -- [`mel.scope`](#binding-to-properties-inside-a-module-or-global): reach to - deeper properties inside a JavaScript object -- [`mel.splice`](#variadic-function-arguments): a deprecated attribute, is an - alternate form of `mel.variadic` -- [`mel.variadic`](#variadic-function-arguments): bind to a function taking - variadic arguments from an array - -These attributes are used to annotate arguments in `external` definitions: - -- [`u`](#binding-to-callbacks): define function arguments as uncurried (manual) -- [`mel.int`](#using-polymorphic-variants-to-bind-to-enums): compile function - argument to an int -- [`mel.string`](#using-polymorphic-variants-to-bind-to-enums): compile function - argument to a string -- [`mel.this`](#modeling-this-based-callbacks): bind to `this` based callbacks -- [`mel.uncurry`](#binding-to-callbacks): define function arguments as uncurried - (automated) -- [`mel.unwrap`](#approach-2-polymorphic-variant-mel-unwrap): unwrap variant - values - -These attributes are used in places like records, fields, arguments, functions, -and more: - -- `mel.as`: redefine the name generated in the JavaScript output code. Used in - [constant function arguments](#constant-values-as-arguments), - [variants](#conversion-functions), polymorphic variants (either [inlined in - external functions](#using-polymorphic-variants-to-bind-to-enums) or [in type - definitions](#polymorphic-variants)) and [record - fields](#objects-with-static-shape-record-like). -- [`deriving`](#generate-getters-setters-and-constructors): generate getters and - setters for some types -- [`mel.inline`](#inlining-constant-values): forcefully inline constant values -- [`optional`](#generate-javascript-objects-with-optional-properties): - translates optional fields in a record to omitted properties in the generated - JavaScript object (combines with `deriving`) - -**Extension nodes:** - -In order to use any of these extension nodes, you will have to add the melange -PPX preprocessor to your project. To do so, add the following to the `dune` -file: - -```dune -(library - (name lib) - (modes melange) - (preprocess - (pps melange.ppx))) -``` - -The same field `preprocess` can be added to `melange.emit`. - -Here is the list of all the extension nodes supported by Melange: - -- [`mel.debugger`](#debugger): insert `debugger` statements -- [`mel.external`](#detect-global-variables): read global values -- [`mel.obj`](#using-jst-objects): create JavaScript object literals -- [`mel.raw`](#generate-raw-javascript): write raw JavaScript code -- [`mel.re`](#regular-expressions): insert regular expressions - -## Generate raw JavaScript - -It is possible to directly write JavaScript code from a Melange file. This is -unsafe, but it can be useful for prototyping or as an escape hatch. - -To do it, we will use the `mel.raw` -[extension](https://v2.ocaml.org/manual/extensionnodes.html): - -```ocaml -let add = [%mel.raw {| - function(a, b) { - console.log("hello from raw JavaScript!"); - return a + b; - } -|}] - -let () = Js.log (add 1 2) -``` -```reasonml -let add = [%mel.raw - {| - function(a, b) { - console.log("hello from raw JavaScript!"); - return a + b; - } -|} -]; - -let () = Js.log(add(1, 2)); -``` - -The `{||}` strings are called ["quoted -strings"](https://ocaml.org/manual/lex.html#quoted-string-id). They are similar -to JavaScript’s template literals, in the sense that they are multi-line, and -there is no need to escape characters inside the string. - -Using one percentage signthe extension name between square brackets -(`[%mel.raw ]`) is useful to define expressions (function bodies, or -other values) where the implementation is directly JavaScript. This is useful as -we can attach the type signature already in the same line, to make our code -safer. For example: - -```ocaml -let f : unit -> int = [%mel.raw "function() {return 1}"] -``` -```reasonml -let f: unit => int = ([%mel.raw "function() {return 1}"]: unit => int); -``` - -Using two percentage signs (`[%%mel.raw -"xxx"]`)the extension name without square -brackets (`%mel.raw "xxx"`) is reserved for definitions in a -[structure](https://v2.ocaml.org/manual/moduleexamples.html#s:module:structures) -or [signature](https://v2.ocaml.org/manual/moduleexamples.html#s%3Asignature). - -For example: - -```ocaml -[%%mel.raw "var a = 1"] -``` -```reasonml -[%%mel.raw "var a = 1"]; -``` - -## Debugger - -Melange allows you to inject a `debugger;` expression using the `mel.debugger` -extension: - -```ocaml -let f x y = - [%mel.debugger]; - x + y -``` -```reasonml -let f = (x, y) => { - [%mel.debugger]; - x + y; -}; -``` - -Output: - -```javascript -function f (x,y) { - debugger; // JavaScript developer tools will set a breakpoint and stop here - return x + y | 0; -} -``` - -## Detect global variables - -Melange provides a relatively type safe approach to use globals that might be -defined either in the JavaScript runtime environment: `mel.external`. - -`[%mel.external id]` will check if the JavaScript value `id` is `undefined` or -not, and return an `Option.t` value accordingly. - -For example: - -```ocaml -let () = match [%mel.external __DEV__] with -| Some _ -> Js.log "dev mode" -| None -> Js.log "production mode" -``` -```reasonml -let () = - switch ([%mel.external __DEV__]) { - | Some(_) => Js.log("dev mode") - | None => Js.log("production mode") - }; -``` - -Another example: - -```ocaml -let () = match [%mel.external __filename] with -| Some f -> Js.log f -| None -> Js.log "non-node environment" -``` -```reasonml -let () = - switch ([%mel.external __filename]) { - | Some(f) => Js.log(f) - | None => Js.log("non-node environment") - }; -``` - -`[%mel.external id]` makes `id` available as a value of type 'a Option.tOption.t('a), meaning its wrapped value is -compatible with any type. If you use the value, it is recommended to annotate it -into a known type first to avoid runtime issues. - -## Inlining constant values - -Some JavaScript idioms require special constants to be inlined since they serve -as de-facto directives for bundlers. A common example is `process.env.NODE_ENV`: - -```js -if (process.env.NODE_ENV !== "production") { - // Development-only code -} -``` - -becomes: - -```js -if ("development" !== "production") { - // Development-only code -} -``` - -In this case, bundlers such as Webpack can tell that the `if` statement always -evaluates to a specific branch and eliminate the others. - -Melange provides the `mel.inline` attribute to achieve the same goal in -generated JavaScript. Let’s look at an example: - -```ocaml -external node_env : string = "NODE_ENV" [@@mel.scope "process", "env"] - -let development = "development" -let () = if node_env <> development then Js.log "Only in Production" - -let development_inline = "development" [@@mel.inline] -let () = if node_env <> development_inline then Js.log "Only in Production" -``` -```reasonml -[@mel.scope ("process", "env")] external node_env: string = "NODE_ENV"; - -let development = "development"; -let () = - if (node_env != development) { - Js.log("Only in Production"); - }; - -[@mel.inline] -let development_inline = "development"; -let () = - if (node_env != development_inline) { - Js.log("Only in Production"); - }; -``` - -As we can see in the generated JavaScript presented below: - -- the `development` variable is emitted - - it gets used as a variable `process.env.NODE_ENV !== development` in the - `if` statement -- the `development_inline` variable isn’t present in the final output - - its value is inlined in the `if` statement: `process.env.NODE_ENV !== - "development"` - -```js -var development = "development"; - -if (process.env.NODE_ENV !== development) { - console.log("Only in Production"); -} - -if (process.env.NODE_ENV !== "development") { - console.log("Only in Production"); -} -``` - -## Bind to JavaScript objects - -JavaScript objects are used in a variety of use cases: - -- As a fixed shape - [record](https://en.wikipedia.org/wiki/Record_\(computer_science\)). -- As a map or dictionary. -- As a class. -- As a module to import/export. - -Melange separates the binding methods for JavaScript objects based on these four -use cases. This section documents the first three. Binding to JavaScript module -objects is described in the ["Using functions from other JavaScript -modules"](#using-functions-from-other-javascript-modules) section. - - - -### Objects with static shape (record-like) - -#### Using OCaml records - -If your JavaScript object has fixed fields, then it’s conceptually like an -[OCaml -record](https://v2.ocaml.org/manual/coreexamples.html#s%3Atut-recvariants). -Since Melange compiles records into JavaScript objects, the most common way to -bind to JavaScript objects is using records. - -```ocaml -type person = { - name : string; - friends : string array; - age : int; -} - -external john : person = "john" [@@mel.module "MySchool"] -let john_name = john.name -``` -```reasonml -type person = { - name: string, - friends: array(string), - age: int, -}; - -[@mel.module "MySchool"] external john: person = "john"; -let john_name = john.name; -``` - -This is the generated JavaScript: - -```js -var MySchool = require("MySchool"); - -var john_name = MySchool.john.name; -``` - -External functions are documented in [a previous section](#external-functions). -The `mel.module` attribute is documented -[here](#using-functions-from-other-javascript-modules). - -If you want or need to use different field names on the Melange and the -JavaScript sides, you can use the `mel.as` decorator: - -```ocaml -type action = { - type_ : string [@mel.as "type"] -} - -let action = { type_ = "ADD_USER" } -``` -```reasonml -type action = { - [@mel.as "type"] - type_: string, -}; - -let action = {type_: "ADD_USER"}; -``` - -Which generates the JavaScript code: - -```js -var action = { - type: "ADD_USER" -}; -``` - -This is useful to map to JavaScript attribute names that cannot be expressed in -Melange, for example, where the JavaScript name we want to generate is a -[reserved keyword](https://v2.ocaml.org/manual/lex.html#sss:keywords). - -It is also possible to map a Melange record to a JavaScript array by passing -indices to the `mel.as` decorator: - -```ocaml -type t = { - foo : int; [@mel.as "0"] - bar : string; [@mel.as "1"] -} - -let value = { foo = 7; bar = "baz" } -``` -```reasonml -type t = { - [@mel.as "0"] - foo: int, - [@mel.as "1"] - bar: string, -}; - -let value = { - foo: 7, - bar: "baz", -}; -``` - -And its JavaScript generated code: - -```js -var value = [ - 7, - "baz" -]; -``` - -#### Using `Js.t` objects - -Alternatively to records, Melange offers another type that can be used to -produce JavaScript objects. This type is 'a -Js.tJs.t('a), where `'a` is an [OCaml -object](https://v2.ocaml.org/manual/objectexamples.html). - -The advantage of objects versus records is that no type declaration is needed in -advance, which can be helpful for prototyping or quickly generating JavaScript -object literals. - -Melange provides some ways to create `Js.t` object values, as well as accessing -the properties inside them. To create values, the `[%mel.obj]` extension is -used, and the `##` infix operator allows to read from the object properties: - -```ocaml -let john = [%mel.obj { name = "john"; age = 99 }] -let t = john##name -``` -```reasonml -let john = { - "name": "john", - "age": 99, -}; -let t = john##name; -``` - -Which generates: - -```js -var john = { - name: "john", - age: 99 -}; - -var t = john.name; -``` - -Note that object types allow for some flexibility that the record types do not -have. For example, an object type can be coerced to another with fewer values or -methods, while it is impossible to coerce a record type to another one with -fewer fields. So different object types that share some methods can be mixed in -a data structure where only their common methods are visible. - -To give an example, one can create a function that operates in all the object -types that include a field `name` that is of type string, e.g.: - -```ocaml -let name_extended obj = obj##name ^ " wayne" - -let one = name_extended [%mel.obj { name = "john"; age = 99 }] -let two = name_extended [%mel.obj { name = "jane"; address = "1 infinite loop" }] -``` -```reasonml -let name_extended = obj => obj##name ++ " wayne"; - -let one = - name_extended({ - "name": "john", - "age": 99, - }); -let two = - name_extended({ - "name": "jane", - "address": "1 infinite loop", - }); -``` - -To read more about objects and polymorphism we recommend checking the [OCaml -docs](https://ocaml.org/docs/objects) or the [OCaml -manual](https://v2.ocaml.org/manual/objectexamples.html). - -#### Using external functions - -We have already explored one approach for creating JavaScript object literals by -using [`Js.t` values and the `mel.obj` extension](#using-jst-objects). - -Melange additionally offers the `mel.obj` attribute, which can be used in -combination with external functions to create JavaScript objects. When these -functions are called, they generate objects with fields corresponding to the -labeled arguments of the function. - -If any of these labeled arguments are defined as optional and omitted during -function application, the resulting JavaScript object will exclude the -corresponding fields. This allows to create runtime objects and control whether -optional keys are emitted at runtime. - -For example, assuming we need to bind to a JavaScript object like this: - -```js -var homeRoute = { - type: "GET", - path: "/", - action: () => console.log("Home"), - // options: ... -}; -``` - -The first three fields are required and the `options` field is optional. You can -declare a binding function like: - -```ocaml -external route : - _type:string -> - path:string -> - action:(string list -> unit) -> - ?options:< .. > -> - unit -> - _ = "" - [@@mel.obj] -``` -```reasonml -[@mel.obj] -external route: - ( - ~_type: string, - ~path: string, - ~action: list(string) => unit, - ~options: {..}=?, - unit - ) => - _; -``` - -Note that the empty string at the end of the function is used to make it -syntactically valid. The value of this string is ignored by the compiler. - -Since there is an optional argument `options`, an additional unlabeled argument -of type `unit` is included after it. It allows to omit the optional argument on -function application. More information about labeled optional arguments can be -found in the [OCaml -manual](https://v2.ocaml.org/manual/lablexamples.html#s:optional-arguments). - -The return type of the function should be left unspecified using the wildcard -type `_`. Melange will automatically infer the type of the resulting JavaScript -object. - -In the route function, the `_type` argument starts with an underscore. When -binding to JavaScript objects with fields that are reserved keywords in OCaml, -Melange allows the use of an underscore prefix for the labeled arguments. The -resulting JavaScript object will have the underscore removed from the field -names. This is only required for the `mel.obj` attribute, while for other cases, -the `mel.as` attribute can be used to rename fields. - -If we call the function like this: - -```ocaml -let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () -``` -```reasonml -let homeRoute = - route(~_type="GET", ~path="/", ~action=_ => Js.log("Home"), ()); -``` - -We get the following JavaScript, which does not include the `options` field -since its argument wasn’t present: - -```javascript -var homeRoute = { - type: "GET", - path: "/", - action: (function (param) { - console.log("Home"); - }) -}; -``` - -#### Bind to object properties - -If you need to bind only to the property of a JavaScript object, you can use -`mel.get` and `mel.set` to access it using the dot notation `.`: - -```ocaml -(* Abstract type for the `document` value *) -type document - -external document : document = "document" - -external set_title : document -> string -> unit = "title" [@@mel.set] -external get_title : document -> string = "title" [@@mel.get] - -let current = get_title document -let () = set_title document "melange" -``` -```reasonml -/* Abstract type for the `document` value */ -type document; - -external document: document = "document"; - -[@mel.set] external set_title: (document, string) => unit = "title"; -[@mel.get] external get_title: document => string = "title"; - -let current = get_title(document); -let () = set_title(document, "melange"); -``` - -This generates: - -```javascript -var current = document.title; -document.title = "melange"; -``` - -Alternatively, if some dynamism is required on the way the property is accessed, -you can use `mel.get_index` and `mel.set_index` to access it using the bracket -notation `[]`: - -```ocaml -type t -external create : int -> t = "Int32Array" [@@mel.new] -external get : t -> int -> int = "" [@@mel.get_index] -external set : t -> int -> int -> unit = "" [@@mel.set_index] - -let () = - let i32arr = (create 3) in - set i32arr 0 42; - Js.log (get i32arr 0) -``` -```reasonml -type t; -[@mel.new] external create: int => t = "Int32Array"; -[@mel.get_index] external get: (t, int) => int; -[@mel.set_index] external set: (t, int, int) => unit; - -let () = { - let i32arr = create(3); - set(i32arr, 0, 42); - Js.log(get(i32arr, 0)); -}; -``` - -Which generates: - -```js -var i32arr = new Int32Array(3); -i32arr[0] = 42; -console.log(i32arr[0]); -``` - -### Objects with dynamic shape (dictionary-like) - -Sometimes JavaScript objects are used as dictionaries. In these cases: - -- All values stored in the object belong to the same type -- Key-value pairs can be added or removed at runtime - -For this particular use case of JavaScript objects, Melange exposes a specific -type `Js.Dict.t`. The values and functions to work with values of this type are -defined in the Js.Dict moduleJs.Dict module, with operations -like `get`, `set`, etc. - -Values of the type `Js.Dict.t` compile to JavaScript objects. - -### JavaScript classes - -JavaScript classes are special kinds of objects. To interact with classes, -Melange exposes `mel.new` to emulate e.g. `new Date()`: - -```ocaml -type t -external create_date : unit -> t = "Date" [@@mel.new] -let date = create_date () -``` -```reasonml -type t; -[@mel.new] external create_date: unit => t = "Date"; -let date = create_date(); -``` - -Which generates: - -```js -var date = new Date(); -``` - -You can chain `mel.new` and `mel.module` if the JavaScript class you want to -work with is in a separate JavaScript module: - -```ocaml -type t -external book : unit -> t = "Book" [@@mel.new] [@@mel.module] -let myBook = book () -``` -```reasonml -type t; -[@mel.new] [@mel.module] external book: unit => t = "Book"; -let myBook = book(); -``` - -Which generates: - -```js -var Book = require("Book"); -var myBook = new Book(); -``` - -## Bind to JavaScript functions or values - -### Using global functions or values - -Binding to a JavaScript function available globally makes use of `external`, -like with objects. But unlike objects, there is no need to add any attributes: - -```ocaml -(* Abstract type for `timeoutId` *) -type timeoutId -external setTimeout : (unit -> unit) -> int -> timeoutId = "setTimeout" -external clearTimeout : timeoutId -> unit = "clearTimeout" - -let id = setTimeout (fun () -> Js.log "hello") 100 -let () = clearTimeout id -``` -```reasonml -/* Abstract type for `timeoutId` */ -type timeoutId; -external setTimeout: (unit => unit, int) => timeoutId = "setTimeout"; -external clearTimeout: timeoutId => unit = "clearTimeout"; - -let id = setTimeout(() => Js.log("hello"), 100); -let () = clearTimeout(id); -``` - -> **_NOTE:_** The bindings to `setTimeout` and `clearTimeout` are shown here for -> learning purposes, but they are already available in the target="_self" href="./api/ml/melange/Js/Global">Js.Global -> module href="./api/re/melange/Js/Global">Js.Global module. - -Generates: - -```javascript -var id = setTimeout(function (param) { - console.log("hello"); -}, 100); - -clearTimeout(id); -``` - -Global bindings can also be applied to values: - -```ocaml -(* Abstract type for `document` *) -type document - -external document : document = "document" -let document = document -``` -```reasonml -/* Abstract type for `document` */ -type document; - -external document: document = "document"; -let document = document; -``` - -Which generates: - -```javascript -var doc = document; -``` - -### Using functions from other JavaScript modules - -`mel.module` allows to bind to values that belong to another JavaScript module. -It accepts a string with the name of the module, or the relative path to it. - -```ocaml -external dirname : string -> string = "dirname" [@@mel.module "path"] -let root = dirname "/User/github" -``` -```reasonml -[@mel.module "path"] external dirname: string => string = "dirname"; -let root = dirname("/User/github"); -``` - -Generates: - -```js -var Path = require("path"); -var root = Path.dirname("/User/github"); -``` - -### Binding to properties inside a module or global - -For cases when we need to create bindings for a property within a module or a -global JavaScript object, Melange provides the `mel.scope` attribute. - -For example, if we want to write some bindings for a specific property -`commands` from [the `vscode` -package](https://code.visualstudio.com/api/references/vscode-api#commands), we -can do: - -```ocaml -type param -external executeCommands : string -> param array -> unit = "" - [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] - -let f a b c = executeCommands "hi" [| a; b; c |] -``` -```reasonml -type param; -[@mel.scope "commands"] [@mel.module "vscode"] [@mel.variadic] -external executeCommands: (string, array(param)) => unit; - -let f = (a, b, c) => executeCommands("hi", [|a, b, c|]); -``` - -Which compiles to: - -```javascript -var Vscode = require("vscode"); - -function f(a, b, c) { - Vscode.commands.executeCommands("hi", a, b, c); -} -``` - -The `mel.scope` attribute can take multiple arguments as payload, in case we -want to reach deeper into the object from the module we are importing. - -For example: - -```ocaml -type t - -external back : t = "back" - [@@mel.module "expo-camera"] [@@mel.scope "Camera", "Constants", "Type"] - -let camera_type_back = back -``` -```reasonml -type t; - -[@mel.module "expo-camera"] [@mel.scope ("Camera", "Constants", "Type")] -external back: t = "back"; - -let camera_type_back = back; -``` - -Which generates: - -```javascript -var ExpoCamera = require("expo-camera"); - -var camera_type_back = ExpoCamera.Camera.Constants.Type.back; -``` - -It can be used without `mel.module`, to created scoped bindings to global -values: - -```ocaml -external imul : int -> int -> int = "imul" [@@mel.scope "Math"] - -let res = imul 1 2 -``` -```reasonml -[@mel.scope "Math"] external imul: (int, int) => int = "imul"; - -let res = imul(1, 2); -``` - -Which produces: - -```javascript -var res = Math.imul(1, 2); -``` - -Or it can be used together with `mel.new`: - -```ocaml -type t - -external create : unit -> t = "GUI" - [@@mel.new] [@@mel.scope "default"] [@@mel.module "dat.gui"] - -let gui = create () -``` -```reasonml -type t; - -[@mel.new] [@mel.scope "default"] [@mel.module "dat.gui"] -external create: unit => t = "GUI"; - -let gui = create(); -``` - -Which generates: - - -```javascript -var DatGui = require("dat.gui"); - -var gui = new (DatGui.default.GUI)(); -``` - -### Labeled arguments - -OCaml has [labeled arguments](https://v2.ocaml.org/manual/lablexamples.html), -which can also be optional, and work with `external` as well. - -Labeled arguments can be useful to provide more information about the arguments -of a JavaScript function that is called from Melange. - -Let’s say we have the following JavaScript function, that we want to call from -Melange: - -```js -// MyGame.js - -function draw(x, y, border) { - // let’s assume `border` is optional and defaults to false -} -draw(10, 20) -draw(20, 20, true) -``` - -When writing Melange bindings, we can add labeled arguments to make things more -clear: - -```ocaml -external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" - [@@mel.module "MyGame"] - -let () = draw ~x:10 ~y:20 ~border:true () -let () = draw ~x:10 ~y:20 () -``` -```reasonml -[@mel.module "MyGame"] -external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"; - -let () = draw(~x=10, ~y=20, ~border=true, ()); -let () = draw(~x=10, ~y=20, ()); -``` - -Generates: - -```js -var MyGame = require("MyGame"); - -MyGame.draw(10, 20, true); -MyGame.draw(10, 20, undefined); -``` - -The generated JavaScript function is the same, but now the usage in Melange is -much clearer. - -**Note**: in this particular case, a final param of type unit, `()` must be -added after `border`, since `border` is an optional argument at the last -position. Not having the last param `unit` would lead to a warning, which is -explained in detail [in the OCaml -documentation](https://ocaml.org/docs/labels#warning-this-optional-argument-cannot-be-erased). - -Note that you can freely reorder the labeled arguments when applying the -function on the Melange side. The generated code will maintain the original -order that was used when declaring the function: - -```ocaml -external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" - [@@mel.module "MyGame"] -let () = draw ~x:10 ~y:20 () -let () = draw ~y:20 ~x:10 () -``` -```reasonml -[@mel.module "MyGame"] -external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"; -let () = draw(~x=10, ~y=20, ()); -let () = draw(~y=20, ~x=10, ()); -``` - -Generates: - -```js -var MyGame = require("MyGame"); - -MyGame.draw(10, 20, undefined); -MyGame.draw(10, 20, undefined); -``` - -### Calling an object method - -If we need to call a JavaScript method, Melange provides the attribute -`mel.send`. - -> In the following snippets, we will be referring to a type `Dom.element`, which -> is provided within the library `melange.dom`. You can add it to your project -> by including `(libraries melange.dom)` to your `dune` file: - -```ocaml -(* Abstract type for the `document` global *) -type document - -external document : document = "document" -external get_by_id : document -> string -> Dom.element = "getElementById" - [@@mel.send] - -let el = get_by_id document "my-id" -``` -```reasonml -/* Abstract type for the `document` global */ -type document; - -external document: document = "document"; -[@mel.send] -external get_by_id: (document, string) => Dom.element = "getElementById"; - -let el = get_by_id(document, "my-id"); -``` - -Generates: - -```js -var el = document.getElementById("my-id"); -``` - -When using `mel.send`, the first argument will be the object that holds the -property with the function we want to call. This combines well with the pipe -first operator \|.\-\>, see the ["Chaining"](#chaining) section -below. - -If we want to design our bindings to be used with OCaml pipe last operator `|>`, -there is an alternate `mel.send.pipe` attribute. Let’s rewrite the example above -using it: - -```ocaml -(* Abstract type for the `document` global *) -type document - -external document : document = "document" -external get_by_id : string -> Dom.element = "getElementById" - [@@mel.send.pipe: document] - -let el = get_by_id "my-id" document -``` -```reasonml -/* Abstract type for the `document` global */ -type document; - -external document: document = "document"; -[@mel.send.pipe: document] -external get_by_id: string => Dom.element = "getElementById"; - -let el = get_by_id("my-id", document); -``` - -Generates the same code as `mel.send`: - -```js -var el = document.getElementById("my-id"); -``` - -#### Chaining - -It is common to find this kind of API in JavaScript: `foo().bar().baz()`. This -kind of API can be designed with Melange externals. Depending on which -convention we want to use, there are two attributes available: - -- For a data-first convention, the `mel.send` attribute, in combination with - [the pipe first operator](#pipe-first) \|.\-\> -- For a data-last convention, the `mel.send.pipe` attribute, in combination with - OCaml [pipe last operator](#pipe-last) `|>`. - -Let’s see first an example of chaining using data-first convention with the pipe -first operator \|.\-\>: - -```ocaml -(* Abstract type for the `document` global *) -type document - -external document : document = "document" -external get_by_id : document -> string -> Dom.element = "getElementById" - [@@mel.send] -external get_by_classname : Dom.element -> string -> Dom.element - = "getElementsByClassName" - [@@mel.send] - -let el = document |. get_by_id "my-id" |. get_by_classname "my-class" -``` -```reasonml -/* Abstract type for the `document` global */ -type document; - -external document: document = "document"; -[@mel.send] -external get_by_id: (document, string) => Dom.element = "getElementById"; -[@mel.send] -external get_by_classname: (Dom.element, string) => Dom.element = - "getElementsByClassName"; - -let el = document->(get_by_id("my-id"))->(get_by_classname("my-class")); -``` - -Will generate: - -```javascript -var el = document.getElementById("my-id").getElementsByClassName("my-class"); -``` - -Now with pipe last operator `|>`: - -```ocaml -(* Abstract type for the `document` global *) -type document - -external document : document = "document" -external get_by_id : string -> Dom.element = "getElementById" - [@@mel.send.pipe: document] -external get_by_classname : string -> Dom.element = "getElementsByClassName" - [@@mel.send.pipe: Dom.element] - -let el = document |> get_by_id "my-id" |> get_by_classname "my-class" -``` -```reasonml -/* Abstract type for the `document` global */ -type document; - -external document: document = "document"; -[@mel.send.pipe: document] -external get_by_id: string => Dom.element = "getElementById"; -[@mel.send.pipe: Dom.element] -external get_by_classname: string => Dom.element = "getElementsByClassName"; - -let el = document |> get_by_id("my-id") |> get_by_classname("my-class"); -``` - -Will generate the same JavaScript as the pipe first version: - -```javascript -var el = document.getElementById("my-id").getElementsByClassName("my-class"); -``` - -### Variadic function arguments - -Sometimes JavaScript functions take an arbitrary amount of arguments. For these -cases, Melange provides the `mel.variadic` attribute, which can be attached to -the `external` declaration. However, there is one caveat: all the variadic -arguments need to belong to the same type. - -```ocaml -external join : string array -> string = "join" - [@@mel.module "path"] [@@mel.variadic] -let v = join [| "a"; "b" |] -``` -```reasonml -[@mel.module "path"] [@mel.variadic] -external join: array(string) => string = "join"; -let v = join([|"a", "b"|]); -``` - -Generates: - -```js -var Path = require("path"); -var v = Path.join("a", "b"); -``` - -If more dynamism is needed, there is a way to inject elements with different -types in the array and still have Melange compile to JavaScript values that are -not wrapped using the OCaml -[`unboxed`](https://v2.ocaml.org/manual/attributes.html) attribute, which was -mentioned [in the OCaml attributes section](#reusing-ocaml-attributes): - -```ocaml -type hide = Hide : 'a -> hide [@@unboxed] - -external join : hide array -> string = "join" [@@mel.module "path"] [@@mel.variadic] - -let v = join [| Hide "a"; Hide 2 |] -``` -```reasonml -[@unboxed] -type hide = - | Hide('a): hide; - -[@mel.module "path"] [@mel.variadic] -external join: array(hide) => string = "join"; - -let v = join([|Hide("a"), Hide(2)|]); -``` - -Compiles to: - -```javascript -var Path = require("path"); - -var v = Path.join("a", 2); -``` - -### Bind to a polymorphic function - -Some JavaScript libraries will define functions where the arguments can vary on -both type and shape. There are two approaches to bind to those, depending on how -dynamic they are. - -#### Approach 1: Multiple external functions - -If it is possible to enumerate the many forms an overloaded JavaScript function -can take, a flexible approach is to bind to each form individually: - -```ocaml -external drawCat : unit -> unit = "draw" [@@mel.module "MyGame"] -external drawDog : giveName:string -> unit = "draw" [@@mel.module "MyGame"] -external draw : string -> useRandomAnimal:bool -> unit = "draw" - [@@mel.module "MyGame"] -``` -```reasonml -[@mel.module "MyGame"] external drawCat: unit => unit = "draw"; -[@mel.module "MyGame"] external drawDog: (~giveName: string) => unit = "draw"; -[@mel.module "MyGame"] -external draw: (string, ~useRandomAnimal: bool) => unit = "draw"; -``` - -Note how all three externals bind to the same JavaScript function, `draw`. - -#### Approach 2: Polymorphic variant + `mel.unwrap` - -In some cases, the function has a constant number of arguments but the type of -the argument can vary. For cases like this, we can model the argument as a -variant and use the `mel.unwrap` attribute in the external. - -Let’s say we want to bind to the following JavaScript function: - -```js -function padLeft(value, padding) { - if (typeof padding === "number") { - return Array(padding + 1).join(" ") + value; - } - if (typeof padding === "string") { - return padding + value; - } - throw new Error(`Expected string or number, got '${padding}'.`); -} -``` - -As the `padding` argument can be either a number or a string, we can use -`mel.unwrap` to define it. It is important to note that `mel.unwrap` imposes -certain requirements on the type it is applied to: - -- It needs to be a [polymorphic - variant](https://v2.ocaml.org/manual/polyvariant.html) -- Its definition needs to be inlined -- Each variant tag needs to have an argument -- The variant type can not be opened (can’t use `>`) - -```ocaml -external padLeft: - string - -> ([ `Str of string - | `Int of int - ] [@mel.unwrap]) - -> string - = "padLeft" - -let _ = padLeft "Hello World" (`Int 4) -let _ = padLeft "Hello World" (`Str "Message from Melange: ") -``` -```reasonml -external padLeft: - ( - string, - [@mel.unwrap] [ - | `Str(string) - | `Int(int) - ] - ) => - string = - "padLeft"; - -let _ = padLeft("Hello World", `Int(4)); -let _ = padLeft("Hello World", `Str("Message from Melange: ")); -``` - -Which produces the following JavaScript: - -```js -padLeft("Hello World", 4); -padLeft("Hello World", "Message from Melange: "); -``` - -As we saw in the [Non-shared data types](#non-shared-data-types) section, we -should rather avoid passing variants directly to the JavaScript side. By using -`mel.unwrap` we get the best of both worlds: from Melange we can use variants, -while JavaScript gets the raw values inside them. - -### Using polymorphic variants to bind to enums - -Some JavaScript APIs take a limited subset of values as input. For example, -Node’s `fs.readFileSync` second argument can only take a few given string -values: `"ascii"`, `"utf8"`, etc. Some other functions can take values from a -few given integers, like the `createStatusBarItem` function in VS Code API, -which can take an `alignment` parameter that can only be [`1` or -`2`](https://github.com/Microsoft/vscode/blob/2362ec665c84a1519162b50c36ed4f29d1e20f62/src/vs/vscode.d.ts#L4098-L4109). - -One could still type these parameters as just `string` or `int`, but this would -not prevent consumers of the external function from calling it using values that -are unsupported by the JavaScript function. Let’s see how we can use polymorphic -variants to avoid runtime errors. - -If the values are strings, we can use the `mel.string` attribute: - -```ocaml -external read_file_sync : - name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" - [@@mel.module "fs"] - -let _ = read_file_sync ~name:"xx.txt" `ascii -``` -```reasonml -[@mel.module "fs"] -external read_file_sync: - ( - ~name: string, - [@mel.string] [ - | `utf8 - | `ascii - ] - ) => - string = - "readFileSync"; - -let _ = read_file_sync(~name="xx.txt", `ascii); -``` - -Which generates: - -```js -var Fs = require("fs"); -Fs.readFileSync("xx.txt", "ascii"); -``` - -This technique can be combined with the `mel.as` attribute to modify the strings -produced from the polymorphic variant values. For example: - -```ocaml -type document -type style - -external document : document = "document" -external get_by_id : document -> string -> Dom.element = "getElementById" -[@@mel.send] -external style : Dom.element -> style = "style" [@@mel.get] -external transition_timing_function : - style -> - ([ `ease - | `easeIn [@mel.as "ease-in"] - | `easeOut [@mel.as "ease-out"] - | `easeInOut [@mel.as "ease-in-out"] - | `linear ] - [@mel.string]) -> - unit = "transitionTimingFunction" -[@@mel.set] - -let element_style = style (get_by_id document "my-id") -let () = transition_timing_function element_style `easeIn -``` -```reasonml -type document; -type style; - -external document: document = "document"; -[@mel.send] -external get_by_id: (document, string) => Dom.element = "getElementById"; -[@mel.get] external style: Dom.element => style = "style"; -[@mel.set] -external transition_timing_function: - ( - style, - [@mel.string] [ - | `ease - | [@mel.as "ease-in"] `easeIn - | [@mel.as "ease-out"] `easeOut - | [@mel.as "ease-in-out"] `easeInOut - | `linear - ] - ) => - unit = - "transitionTimingFunction"; - -let element_style = style(get_by_id(document, "my-id")); -let () = transition_timing_function(element_style, `easeIn); -``` - -This will generate: - -```javascript -var element_style = document.getElementById("my-id").style; - -element_style.transitionTimingFunction = "ease-in"; -``` - -Aside from producing string values, Melange also offers `mel.int` to produce -integer values. `mel.int` can also be combined with `mel.as`: - -```ocaml -external test_int_type : - ([ `on_closed | `on_open [@mel.as 20] | `in_bin ][@mel.int]) -> int - = "testIntType" - -let value = test_int_type `on_open -``` -```reasonml -external test_int_type: - ( - [@mel.int] - [ - | `on_closed - | [@mel.as 20] `on_open - | `in_bin - ] - ) => - int = - "testIntType"; - -let value = test_int_type(`on_open); -``` - -In this example, `on_closed` will be encoded as 0, `on_open` will be 20 due to -the attribute `mel.as` and `in_bin` will be 21, because if no `mel.as` -annotation is provided for a variant tag, the compiler continues assigning -values counting up from the previous one. - -This code generates: - -```js -var value = testIntType(20); -``` - -### Using polymorphic variants to bind to event listeners - -Polymorphic variants can also be used to wrap event listeners, or any other kind -of callback, for example: - -```ocaml -type readline - -external on : - readline -> - ([ `close of unit -> unit | `line of string -> unit ][@mel.string]) -> - readline = "on" - [@@mel.send] - -let register rl = - rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) -``` -```reasonml -type readline; - -[@mel.send] -external on: - ( - readline, - [@mel.string] [ - | `close(unit => unit) - | `line(string => unit) - ] - ) => - readline = - "on"; - -let register = rl => - rl->(on(`close(event => ())))->(on(`line(line => Js.log(line)))); -``` - -This generates: - -```js -function register(rl) { - return rl - .on("close", function($$event) {}) - .on("line", function(line) { - console.log(line); - }); -} -``` - -### Constant values as arguments - -Sometimes we want to call a JavaScript function and make sure one of the -arguments is always constant. For this, the `[@mel.as]` attribute can be -combined with the wildcard pattern `_`: - -```ocaml -external process_on_exit : (_[@mel.as "exit"]) -> (int -> unit) -> unit - = "process.on" - -let () = - process_on_exit (fun exit_code -> - Js.log ("error code: " ^ string_of_int exit_code)) -``` -```reasonml -external process_on_exit: ([@mel.as "exit"] _, int => unit) => unit = - "process.on"; - -let () = - process_on_exit(exit_code => - Js.log("error code: " ++ string_of_int(exit_code)) - ); -``` - -This generates: - -```js -process.on("exit", function (exitCode) { - console.log("error code: " + exitCode.toString()); -}); -``` - -The `mel.as "exit"` and the wildcard `_` pattern together will tell Melange to -compile the first argument of the JavaScript function to the string `"exit"`. - -You can also use any JSON literal by passing a quoted string to `mel.as`: -`mel.as {json|true|json}` or `mel.as {json|{"name": "John"}|json}`. - -### Binding to callbacks - -In OCaml, all functions have arity 1. This means that if you define a function -like this: - -```ocaml -let add x y = x + y -``` -```reasonml -let add = (x, y) => x + y; -``` - -Its type will be `int -> int -> int`. This means that one can partially apply -`add` by calling `add 1`, which will return another function expecting the -second argument of the addition. This kind of functions are called "curried" -functions, more information about currying in OCaml can be found in [this -chapter](https://cs3110.github.io/textbook/chapters/hop/currying.html) of the -"OCaml Programming: Correct + Efficient + Beautiful" book. - -This is incompatible with how function calling conventions work in JavaScript, -where all function calls always apply all the arguments. To continue the -example, let’s say we have an `add` function implemented in JavaScript, similar -to the one above: - -```javascript -var add = function (a, b) { - return a + b; -}; -``` - -If we call `add(1)`, the function will be totally applied, with `b` having -`undefined` value. And as JavaScript will try to add `1` with `undefined`, we -will get `NaN` as a result. - -To illustrate this difference and how it affects Melange bindings, let’s say we -want to write bindings for a JavaScript function like this: - -```javascript -function map (a, b, f){ - var i = Math.min(a.length, b.length); - var c = new Array(i); - for(var j = 0; j < i; ++j){ - c[j] = f(a[i],b[i]) - } - return c ; -} -``` - -A naive external function declaration could be as below: - -```ocaml -external map : 'a array -> 'b array -> ('a -> 'b -> 'c) -> 'c array = "map" -``` -```reasonml -external map: (array('a), array('b), ('a, 'b) => 'c) => array('c) = "map"; -``` - -Unfortunately, this is not completely correct. The issue is in the callback -function, with type `'a -> 'b -> 'c`. This means that `map` will expect a -function like `add` described above. But as we said, in OCaml, having two -arguments means just to have two functions that take one argument. - -Let’s rewrite `add` to make the problem a bit more clear: - -```ocaml -let add x = let partial y = x + y in partial -``` -```reasonml -let add = x => { - let partial = y => x + y; - partial; -}; -``` - -This will be compiled to: - -```javascript -function add(x) { - return (function (y) { - return x + y | 0; - }); -} -``` - -Now if we ever used our external function `map` with our `add` function by -calling `map arr1 arr2 add` it would not work as expected. JavaScript function -application does not work the same as in OCaml, so the function call in the -`map` implementation, `f(a[i],b[i])`, would be applied over the outer JavaScript -function `add`, which only takes one argument `x`, and `b[i]` would be just -discarded. The value returned from the operation would not be the addition of -the two numbers, but rather the inner anonymous callback. - -To solve this mismatch between OCaml and JavaScript functions and their -application, Melange provides a special attribute `@u` that can be used to -annotate external functions that need to be "uncurried". - -In Reason syntax, this attribute does not need to be -written explicitly, as it is deeply integrated with the Reason parser. To -specify some function type as "uncurried", one just needs to add the dot -character `.` to the function type. For example, `(. 'a, 'b) => 'c` instead of -`('a, 'b) => 'c`. - -In the example above: - -```ocaml -external map : 'a array -> 'b array -> (('a -> 'b -> 'c)[@u]) -> 'c array - = "map" -``` -```reasonml -external map: (array('a), array('b), (. 'a, 'b) => 'c) => array('c) = "map"; -``` - -Here `('a -> 'b -> 'c [@u])``(. 'a, 'b) => 'c`will be interpreted as having -arity 2. In general, `'a0 -> 'a1 ...​ 'aN -> 'b0 [@u]` -is the same as `'a0 -> 'a1 ...​ 'aN -> 'b0``. -'a0, 'a1, ...​ 'aN => 'b0` is the same as `'a0, 'a1, ...​ 'aN => 'b0` -except the former’s arity is guaranteed to be N while the latter is unknown. - -If we try now to call `map` using `add`: - -```ocaml -let add x y = x + y -let _ = map [||] [||] add -``` -```reasonml -let add = (x, y) => x + y; -let _ = map([||], [||], add); -``` -We will get an error: - -```text -let _ = map [||] [||] add - ^^^ -This expression has type int -> int -> int -but an expression was expected of type ('a -> 'b -> 'c) Js.Fn.arity2 -``` - -To solve this, we add `@u``.` in the function definition as well: - -```ocaml -let add = fun [@u] x y -> x + y -``` -```reasonml -let add = (. x, y) => x + y; -``` - -Annotating function definitions can be quite cumbersome when writing a lot of -externals. - -To work around the verbosity, Melange offers another attribute called -`mel.uncurry`. - -Let’s see how we could use it in the previous example. We just need to replace -`u` with `mel.uncurry`: - -```ocaml -external map : - 'a array -> 'b array -> (('a -> 'b -> 'c)[@mel.uncurry]) -> 'c array = "map" -``` -```reasonml -external map: - (array('a), array('b), [@mel.uncurry] (('a, 'b) => 'c)) => array('c) = - "map"; -``` - -Now if we try to call `map` with a regular `add` function: - -```ocaml -let add x y = x + y -let _ = map [||] [||] add -``` -```reasonml -let add = (x, y) => x + y; -let _ = map([||], [||], add); -``` - -Everything works fine now, without having to attach any attributes to `add`. - -The main difference between `u` and `mel.uncurry` is that the latter only works -with externals. `mel.uncurry` is the recommended option to use for bindings, -while `u` remains useful for those use cases where performance is crucial and we -want the JavaScript functions generated from OCaml ones to not be applied -partially. - -### Modeling `this`\-based Callbacks - -Many JavaScript libraries have callbacks which rely on the [`this` -keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this), -for example: - -```js -x.onload = function(v) { - console.log(this.response + v) -} -``` - -Inside the `x.onload` callback, `this` would be pointing to `x`. It would not be -correct to declare `x.onload` of type `unit -> unit`. Instead, Melange -introduces a special attribute, `mel.this`, which allows to type `x` as this: - -```ocaml -type x -external x : x = "x" -external set_onload : x -> ((x -> int -> unit)[@mel.this]) -> unit = "onload" - [@@mel.set] -external resp : x -> int = "response" [@@mel.get] -let _ = - set_onload x - begin - fun [@mel.this] o v -> Js.log (resp o + v) - end -``` -```reasonml -type x; -external x: x = "x"; -[@mel.set] -external set_onload: (x, [@mel.this] ((x, int) => unit)) => unit = "onload"; -[@mel.get] external resp: x => int = "response"; -let _ = set_onload(x, [@mel.this] (o, v) => Js.log(resp(o) + v)); -``` - -Which generates: - -```javascript -x.onload = function (v) { - var o = this; - console.log((o.response + v) | 0); -}; -``` - -Note that the first argument will be reserved for `this`. - -### Wrapping returned nullable values - -JavaScript models `null` and `undefined` differently, whereas it can be useful -to treat both as `'a option``option('a)` in Melange. - -Melange understands the `mel.return` attribute in externals to model how -nullable return types should be wrapped at the bindings boundary. An `external` -value with `mel.return` converts the return value to an `option` type, avoiding -the need for extra wrapping / unwrapping with functions such as -`Js.Nullable.toOption`. - -```ocaml -type element -type document -external get_by_id : document -> string -> element option = "getElementById" - [@@mel.send] [@@mel.return nullable] - -let test document = - let elem = get_by_id document "header" in - match elem with - | None -> 1 - | Some _element -> 2 -``` -```reasonml -type element; -type document; -[@mel.send] [@mel.return nullable] -external get_by_id: (document, string) => option(element) = "getElementById"; - -let test = document => { - let elem = get_by_id(document, "header"); - switch (elem) { - | None => 1 - | Some(_element) => 2 - }; -}; -``` - -Which generates: - -```js -function test($$document) { - var elem = $$document.getElementById("header"); - if (elem == null) { - return 1; - } else { - return 2; - } -} -``` - -The `mel.return` attribute takes an attribute payload, as seen with `[@@mel.return nullable]``[@mel.return nullable]` above. Currently 4 -directives are supported: `null_to_opt`, `undefined_to_opt`, `nullable` and -`identity`. - -`nullable` is encouraged, as it will convert from `null` and `undefined` to -`option` type. - - - -`identity` will make sure that compiler will do nothing about the returned -value. It is rarely used, but introduced here for debugging purposes. - -## Generate getters, setters and constructors - -As we saw in a [previous section](#non-shared-data-types), there are some types -in Melange that compile to values that are not easy to manipulate from -JavaScript. To facilitate the communication from JavaScript code with values of -these types, Melange includes an attribute `deriving` that helps generating -conversion functions, as well as functions to create values from these types. In -particular, for variants and polymorphic variants. - -Additionally, `deriving` can be used with record types, to generate setters and -getters as well as creation functions. - -### Variants - -#### Creating values - -Use `@deriving accessors` on a variant type to create constructor values for -each branch. - -```ocaml -type action = - | Click - | Submit of string - | Cancel -[@@deriving accessors] -``` -```reasonml -[@deriving accessors] -type action = - | Click - | Submit(string) - | Cancel; -``` - -Melange will generate one `let` definition for each variant tag, implemented as -follows: - -- For variant tags with payloads, it will be a function that takes the payload - value as a parameter. -- For variant tags without payloads, it will be a constant with the runtime - value of the tag. - -Given the `action` type definition above, annotated with `deriving`, Melange -will generate something similar to the following code: - -```ocaml -type action = - | Click - | Submit of string - | Cancel - -let click = (Click : action) -let submit param = (Submit param : action) -let cancel = (Cancel : action) -``` -```reasonml -type action = - | Click - | Submit(string) - | Cancel; - -let click: action = Click; -let submit = (param): action => Submit(param); -let cancel: action = Cancel; -``` - -Which will result in the following JavaScript code after compilation: - -```javascript -function submit(param_0) { - return /* Submit */{ - _0: param_0 - }; -} - -var click = /* Click */0; - -var cancel = /* Cancel */1; -``` - -Note the generated definitions are lower-cased, and they can be safely used from -JavaScript code. For example, if the above JavaScript generated code was located -in a `generators.js` file, the definitions can be used like this: - -```javascript -const generators = require('./generators.js'); - -const hello = generators.submit("Hello"); -const click = generators.click; -``` - -#### Conversion functions - -Use `@deriving jsConverter` on a variant type to create converter functions that -allow to transform back and forth between JavaScript integers and Melange -variant values. - -There are a few differences with `@deriving accessors`: - -- `jsConverter` works with the `mel.as` attribute, while `accessors` does not -- `jsConverter` does not support variant tags with payload, while `accessors` - does -- `jsConverter` generates functions to transform values back and forth, while - `accessors` generates functions to create values - -Let’s see a version of the previous example, adapted to work with `jsConverter` -given the constraints above: - -```ocaml -type action = - | Click - | Submit [@mel.as 3] - | Cancel -[@@deriving jsConverter] -``` -```reasonml -[@deriving jsConverter] -type action = - | Click - | [@mel.as 3] Submit - | Cancel; -``` - -This will generate a couple of functions with the following types: - -```ocaml -val actionToJs : action -> int - -val actionFromJs : int -> action option -``` -```reasonml -external actionToJs: action => int = ; - -external actionFromJs: int => option(action) = ; -``` - -`actionToJs` returns integers from values of `action` type. It will start with 0 -for `Click`, 3 for `Submit` (because it was annotated with `mel.as`), and then 4 -for `Cancel`, in the same way that we described when [using `mel.int` with -polymorphic variants](#using-polymorphic-variants-to-bind-to-enums). - -`actionFromJs` returns a value of type `option`, because not every integer can -be converted into a variant tag of the `action` type. - -##### Hide runtime types - -For extra type safety, we can hide the runtime representation of variants -(`int`) from the generated functions, by using `jsConverter { newType }` payload -with `@deriving`: - -```ocaml -type action = - | Click - | Submit [@mel.as 3] - | Cancel -[@@deriving jsConverter { newType }] -``` -```reasonml -[@deriving jsConverter({newType: newType})] -type action = - | Click - | [@mel.as 3] Submit - | Cancel; -``` - -This feature relies on [abstract types](#abstract-types) to hide the JavaScript -runtime representation. It will generate functions with the following types: - -```ocaml -val actionToJs : action -> abs_action - -val actionFromJs : abs_action -> action -``` -```reasonml -external actionToJs: action => abs_action = ; - -external actionFromJs: abs_action => action = ; -``` - -In the case of `actionFromJs`, the return value, unlike the previous case, is -not an option type. This is an example of "correct by construction": the only -way to create an `abs_action` is by calling the `actionToJs` function. - -### Polymorphic variants - -The `@deriving jsConverter` attribute is applicable to polymorphic variants as -well. - -> **_NOTE:_** Similarly to variants, the `@deriving jsConverter` attribute -> cannot be used when the polymorphic variant tags have payloads. Refer to the -> [section on runtime representation](#data-types-and-runtime-representation) to -> learn more about how polymorphic variants are represented in JavaScript. - -Let’s see an example: - -```ocaml -type action = - [ `Click - | `Submit [@mel.as "submit"] - | `Cancel - ] -[@@deriving jsConverter] -``` -```reasonml -[@deriving jsConverter] -type action = [ - | `Click - | [@mel.as "submit"] `Submit - | `Cancel -]; -``` - -Akin to the variant example, the following two functions will be generated: - -```ocaml -val actionToJs : action -> string - -val actionFromJs : string -> action option -``` -```reasonml -external actionToJs: action => string = ; - -external actionFromJs: string => option(action) = ; -``` - -The `jsConverter { newType }` payload can also be used with polymorphic -variants. - -### Records - -#### Accessing fields - -Use `@deriving accessors` on a record type to create accessor functions for its -record field names. - -```ocaml -type pet = { name : string } [@@deriving accessors] - -let pets = [| { name = "Brutus" }; { name = "Mochi" } |] - -let () = pets |. Belt.Array.map name |. Js.Array.join ~sep:"&" |. Js.log -``` -```reasonml -[@deriving accessors] -type pet = {name: string}; - -let pets = [|{name: "Brutus"}, {name: "Mochi"}|]; - -let () = pets->(Belt.Array.map(name))->(Js.Array.join(~sep="&"))->Js.log; -``` - -Melange will generate a function for each field defined in the record. In this -case, a function `name` that allows to get that field from any record of type -`pet`: - - -```ocaml -let name (param : pet) = param.name -``` -```reasonml -let name = (param: pet) => param.name; -``` - -Considering all the above, the produced JavaScript will be: - -```js -function name(param) { - return param.name; -} - -var pets = [ - { - name: "Brutus" - }, - { - name: "Mochi" - } -]; - -console.log(Belt_Array.map(pets, name).join("&")); -``` - -#### Generate JavaScript objects with optional properties - -In some occasions, we might want to emit a JavaScript object where some of the -keys can be conditionally present or absent. - -For instance, consider the following record: - -```ocaml -type person = { - name : string; - age : int option; -} -``` -```reasonml -type person = { - name: string, - age: option(int), -}; -``` - -An example of this use-case would be expecting `{ name = "John"; age = None }` -to generate a JavaScript object such as `{name: "Carl"}`, where the `age` key -doesn’t appear. - -The `@deriving jsProperties` attribute exists to solve this problem. When -present in a record type, `@deriving jsProperties` generates a constructor -function for creating values of the type, where the fields marked with -`[@mel.optional]` will be fully removed from the generated JavaScript object -when their value is `None`. - -Let’s see an example. Considering this Melange code: - -```ocaml -type person = { - name : string; - age : int option; [@mel.optional] -} -[@@deriving jsProperties] -``` -```reasonml -[@deriving jsProperties] -type person = { - name: string, - [@mel.optional] - age: option(int), -}; -``` - -Melange will generate a constructor to create values of this type. In our -example, the OCaml signature would look like this after preprocessing: - -```ocaml -type person - -val person : name:string -> ?age:int -> unit -> person -``` -```reasonml -type person; - -external person: (~name: string, ~age: int=?, unit) => person = ; -``` - -The `person` function can be used to create values of `person`. It is the only -possible way to create values of this type, since Melange makes it abstract. -Using literals like `{ name = "Alice"; age = None }` directly doesn’t type -check. - -Here is an example of how we can use it: - - -```ocaml -let alice = person ~name:"Alice" ~age:20 () -let bob = person ~name:"Bob" () -``` -```reasonml -let alice = person(~name="Alice", ~age=20, ()); -let bob = person(~name="Bob", ()); -``` - -This will generate the following JavaScript code. Note how there is no -JavaScript runtime overhead: - -```js -var alice = { - name: "Alice", - age: 20 -}; - -var bob = { - name: "Bob" -}; -``` - -The `person` function uses labeled arguments to represent record fields. Because -there is an optional argument `age`, it takes a last argument of type `unit`. -This non-labeled argument allows to omit the optional argument on function -application. Further details about optional labeled arguments can be found in -[the corresponding section of the OCaml -manual](https://v2.ocaml.org/manual/lablexamples.html#s:optional-arguments). - -#### Generating getters and setters - -In case we need both getters and setters for a record, we can use `deriving -getSet` to have them generated for free. - -If we take a record like this: - -```ocaml -type person = { - name : string; - age : int option; [@mel.optional] -} -[@@deriving getSet] -``` -```reasonml -[@deriving getSet] -type person = { - name: string, - [@mel.optional] - age: option(int), -}; -``` - -The `deriving` attribute can combine multiple derivers, for example we can -combine `jsProperties` with `getSet`: - -```ocaml -type person = { - name : string; - age : int option; [@mel.optional] -} -[@@deriving jsProperties, getSet] -``` -```reasonml -[@deriving (jsProperties, getSet)] -type person = { - name: string, - [@mel.optional] - age: option(int), -}; -``` - -When using `getSet`, Melange will create functions `nameGet` and `ageGet`, as -accessors for each record field. - - -```ocaml -let twenty = ageGet alice - -let bob = nameGet bob -``` -```reasonml -let twenty = ageGet(alice); - -let bob = nameGet(bob); -``` - -This generates: - -```javascript -var twenty = alice.age; - -var bob = bob.name; -``` - -The functions are named by appending `Get` to the field names of the record to -prevent potential clashes with other values within the module. If shorter names -are preferred for the getter functions, there is an alternate getSet { light }getSet({light: light}) payload that can be passed -to `deriving`: - -```ocaml -type person = { - name : string; - age : int; -} -[@@deriving jsProperties, getSet { light }] - -let alice = person ~name:"Alice" ~age:20 -let aliceName = name alice -``` -```reasonml -[@deriving (jsProperties, getSet({light: light}))] -type person = { - name: string, - age: int, -}; - -let alice = person(~name="Alice", ~age=20); -let aliceName = name(alice); -``` - -Which generates: - -```javascript -var alice = { - name: "Alice", - age: 20 -}; - -var aliceName = alice.name; -``` - -In this example, the getter functions share the same names as the object fields. -Another distinction from the previous example is that the `person` constructor -function no longer requires the final `unit` argument since we have excluded the -optional field in this case. - -> **_NOTE:_** The `mel.as` attribute can still be applied to record fields when -> the record type is annotated with `deriving`, allowing for the renaming of -> fields in the resulting JavaScript objects, as demonstrated in the section -> about [binding to objects with static -> shape](#objects-with-static-shape-record-like). However, the option to pass -> indices to the `mel.as` decorator (like `[@mel.as "0"]`) to change the runtime -> representation to an array is not available when using `deriving`. - -##### Compatibility with OCaml features - -The `@deriving getSet` attribute and its lightweight variant can be used with -[mutable -fields](https://v2.ocaml.org/manual/coreexamples.html#s:imperative-features) and -[private types](https://v2.ocaml.org/manual/privatetypes.html), which are -features inherited by Melange from OCaml. - -When the record type has mutable fields, Melange will generate setter functions -for them. For example: - -```ocaml -type person = { - name : string; - mutable age : int; -} -[@@deriving getSet] - -let alice = person ~name:"Alice" ~age:20 - -let () = ageSet alice 21 -``` -```reasonml -[@deriving getSet] -type person = { - name: string, - mutable age: int, -}; - -let alice = person(~name="Alice", ~age=20); - -let () = ageSet(alice, 21); -``` - -This will generate: - -```javascript -var alice = { - name: "Alice", - age: 20 -}; - -alice.age = 21; -``` - -If the `mutable` keyword is omitted from the interface file, Melange will not -include the setter function in the module signature, preventing other modules -from mutating any values from the type. - -Private types can be used to prevent Melange from creating the constructor -function. For example, if we define `person` type as private: - -```ocaml -type person = private { - name : string; - age : int; -} -[@@deriving getSet] -``` -```reasonml -[@deriving getSet] -type person = - pri { - name: string, - age: int, - }; -``` - -The accessors `nameGet` and `ageGet` will still be generated, but not the -constructor `person`. This is useful when binding to JavaScript objects while -preventing any Melange code from creating values of such type. - -## Use Melange code from JavaScript - -As mentioned in the [build system -section](build-system.md#commonjs-or-es6-modules), Melange allows to produce -both CommonJS and ES6 modules. In both cases, using Melange-generated JavaScript -code from any hand-written JavaScript file works as expected. - -The following definition: - -```ocaml -let print name = "Hello" ^ name -``` -```reasonml -let print = name => "Hello" ++ name; -``` - -Will generate this JavaScript code, when using CommonJS (the default): - -```js -function print(name) { - return "Hello" + name; -} - -exports.print = print; -``` - -When using ES6 (through the `(module_systems es6)` field in `melange.emit`) this -code will be generated: - -```js -function print(name) { - return "Hello" + name; -} - -export { - print , -} -``` - -So one can use either `require` or `import` (depending on the module system of -choice) to import the `print` value in a JavaScript file. - -### Default ES6 values - -One special case occur when working with JavaScript imports in ES6 modules that -look like this: - -```js -import ten from 'numbers.js'; -``` - -This import expects `numbers.js` to have a default export, like: - -```js -export default ten = 10; -``` - -To emulate this kind of exports from Melange, one just needs to define a -`default` value. - -For example, in a file named numbers.mlnumbers.re: - -```ocaml -let default = 10 -``` -```reasonml -let default = 10; -``` - -That way, Melange will set the value on the `default` export so it can be -consumed as default import on the JavaScript side. - -## Bindings cookbook - -### Globals - -#### `window`: global variable - -```ocaml -external window : Dom.window = "window" -``` -```reasonml -external window: Dom.window = "window"; -``` - -See the [Using global functions or values](#using-global-functions-or-values) -section for more information. - -#### `window?`: does global variable exist - -```ocaml -let _ = match [%mel.external window] with -| Some _ -> "window exists" -| None -> "window does not exist" -``` -```reasonml -let _ = - switch ([%mel.external window]) { - | Some(_) => "window exists" - | None => "window does not exist" - }; -``` - -See the [Detect global variables](#detect-global-variables) section for more -information. - -#### `Math.PI`: variable in global module - -```ocaml -external pi : float = "PI" [@@mel.scope "Math"] -``` -```reasonml -[@mel.scope "Math"] external pi: float = "PI"; -``` - -See the [Binding to properties inside a module or -global](#binding-to-properties-inside-a-module-or-global) section for more -information. - -#### `console.log`: function in global module - -```ocaml -external log : 'a -> unit = "log" [@@mel.scope "console"] -``` -```reasonml -[@mel.scope "console"] external log: 'a => unit = "log"; -``` - -See the [Binding to properties inside a module or -global](#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 - -```ocaml -external join : string -> string -> string = "join" [@@mel.module "path"] -let dir = join "a" "b" -``` -```reasonml -[@mel.module "path"] external join: (string, string) => string = "join"; -let dir = join("a", "b"); -``` - -See the [Using functions from other JavaScript -modules](#using-functions-from-other-javascript-modules) section for more -information. - -#### `const foo = require('foo'); foo(1)`: import entire module as a value - -```ocaml -external foo : int -> unit = "foo" [@@mel.module] -let () = foo 1 -``` -```reasonml -[@mel.module] external foo: int => unit = "foo"; -let () = foo(1); -``` - -See the [Using functions from other JavaScript -modules](#using-functions-from-other-javascript-modules) section for more -information. - -#### `import foo from 'foo'; foo(1)`: import ES6 module default export - -```ocaml -external foo : int -> unit = "default" [@@mel.module "foo"] -let () = foo 1 -``` -```reasonml -[@mel.module "foo"] external foo: int => unit = "default"; -let () = foo(1); -``` - -See the [Using functions from other JavaScript -modules](#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 - -```ocaml -module Foo = struct - module Bar = struct - external baz : unit -> unit = "baz" [@@mel.module "foo"] [@@mel.scope "bar"] - end -end - -let () = Foo.Bar.baz () -``` -```reasonml -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](#binding-to-properties-inside-a-module-or-global) section for more -information. - -### Functions - -#### `const dir = path.join('a', 'b', ...)`: function with rest args - -```ocaml -external join : string array -> string = "join" [@@mel.module "path"] [@@mel.variadic] -let dir = join [| "a"; "b" |] -``` -```reasonml -[@mel.module "path"] [@mel.variadic] -external join: array(string) => string = "join"; -let dir = join([|"a", "b"|]); -``` - -See the [Variadic function arguments](#variadic-function-arguments) section for -more information. - -#### `const nums = range(start, stop, step)`: call a function with named arguments for readability - -```ocaml -external range : start:int -> stop:int -> step:int -> int array = "range" -let nums = range ~start:1 ~stop:10 ~step:2 -``` -```reasonml -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 - -```ocaml -external fooString : string -> unit = "foo" -external fooBool : bool -> unit = "foo" - -let () = fooString "" -let () = fooBool true -``` -```reasonml -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) - -```ocaml -external range : start:int -> stop:int -> ?step:int -> unit -> int array - = "range" - -let nums = range ~start:1 ~stop:10 () -``` -```reasonml -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 - -```ocaml -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 ()) () -``` -```reasonml -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](#using-external-functions) section for more information. - -#### `forEach(start, stop, item => console.log(item))`: model a callback - -```ocaml -external forEach : - start:int -> stop:int -> ((int -> unit)[@mel.uncurry]) -> unit = "forEach" - -let () = forEach ~start:1 ~stop:10 Js.log -``` -```reasonml -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](#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: - -```ocaml -let person = [%mel.obj { id = 1; name = "Alice" }] -``` -```reasonml -let person = { - "id": 1, - "name": "Alice", -}; -``` - -See the [Using `Js.t` objects](#using-jst-objects) section for more information. - -Alternatively, for greater type accuracy, one can create a record type and a -value: - -```ocaml -type person = { id : int; name : string } -let person = { id = 1; name = "Alice" } -``` -```reasonml -type person = { - id: int, - name: string, -}; -let person = { - id: 1, - name: "Alice", -}; -``` - -See the [Using OCaml records](#using-ocaml-records) section for more -information. - -#### `person.name`: get a prop - - -```ocaml -let name = person##name -``` -```reasonml -let name = person##name; -``` - -Alternatively, if `person` value is of record type as mentioned in the section -above: - - -```ocaml -let name = person.name -``` -```reasonml -let name = person.name; -``` - -#### `person.id = 0`: set a prop - - -```ocaml -external set_id : person -> int -> unit = "id" [@@mel.set] - -let () = set_id person 0 -``` -```reasonml -[@mel.set] external set_id: (person, int) => unit = "id"; - -let () = set_id(person, 0); -``` - -#### `const {id, name} = person`: object with destructuring - -```ocaml -type person = { id : int; name : string } - -let person = { id = 1; name = "Alice" } -let { id; name } = person -``` -```reasonml -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 - -```javascript -const foo = new Foo(); -foo.bar(); -``` - -You will write: - - -```ocaml -let foo = Foo.make () -let () = Foo.bar foo -``` -```reasonml -let foo = Foo.make(); -let () = Foo.bar(foo); -``` - -Note that many of the techniques shown in the [Functions](#functions) section -are applicable to the instance members shown below. - -#### `const foo = new Foo()`: call a class constructor - -```ocaml -module Foo = struct - type t - external make : unit -> t = "Foo" [@@mel.new] -end - -let foo = Foo.make () -``` -```reasonml -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](#abstract-types) 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](#javascript-classes) section for more information. - -#### `const bar = foo.bar`: get an instance property - -```ocaml -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 -``` -```reasonml -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](#bind-to-object-properties) section for -more information. - -#### `foo.bar = 1`: set an instance property - -```ocaml -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 -``` -```reasonml -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 - -```ocaml -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 -``` -```reasonml -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](#calling-an-object-method) section for more -information. - -#### `const newStr = str.replace(substr, newSubstr)`: non-mutating instance method - -```ocaml -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 -``` -```reasonml -[@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](#calling-an-object-method) section for more -information. - -#### `arr.sort(compareFunction)`: mutating instance method - - -```ocaml -external sort : 'a array -> (('a -> 'a -> int)[@mel.uncurry]) -> 'a array - = "sort" -[@@mel.send] - -let _ = sort arr compare -``` -```reasonml -[@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 +features to create *bindings* to JavaScript code. A binding is a piece of code +that references a JavaScript object or value and makes it available for use in +Melange code. A very basic example of a binding is: ```ocaml -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 +external confirm : string -> bool = "window.confirm" ``` ```reasonml -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](#non-shared-data-types) section for more -information. - -#### `foo.bar == null`: check for null or undefined - -```ocaml -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 +external confirm: string => bool = "window.confirm"; ``` -```reasonml -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 - }; -``` +The above code binds JavaScript's +[window.confirm](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +function to a Melange function named `confirm`. Note how the binding includes +type annotations so that there's no ambiguity about the types of the arguments +and return value. -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. +In this section: -See the [Wrapping returned nullable values](#wrapping-returned-nullable-values) -section for more information. +- [Language concepts](./language-concepts.md): Learn about the language features + that make bindings possible +- [Data types and runtime representations](./data-types-and-runtime-rep.md): See + how Melange types map to JavaScript runtime types +- [Melange attributes and extension nodes](./attributes-and-extension-nodes.md): + A reference of all the built-in attributes and extension nodes that can be + used to create bindings +- [Working with JavaScript objects and + values](./working-with-js-objects-and-values.md): A rundown of all the common + scenarios when binding to JavaScript objects and values (including functions) +- [Advanced JavaScript interoperability](./advanced-js-interop.md): Advanced + topics such as code generation using `@deriving` +- [Bindings cookbook](./bindings-cookbook.md): A collection of recipes that show + JavaScript snippets and the equivalent code in Melange diff --git a/docs/data-types-and-runtime-rep.md b/docs/data-types-and-runtime-rep.md new file mode 100644 index 000000000..7e46f6c44 --- /dev/null +++ b/docs/data-types-and-runtime-rep.md @@ -0,0 +1,294 @@ +# 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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) | +| Option.t `None` | `undefined` | +| 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}` | +| 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: + +```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](#shared-types), which have a transparent representation as JavaScript +values, and then go through the [non-shared types](#non-shared-data-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](https://v2.ocaml.org/manual/lex.html#sss:stringliterals) 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](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) +with a 53-bit mantissa and exponents from -1022 to 1023. This happens to be the +same encoding as [JavaScript +numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding), +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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#fixed-width_number_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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding), +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](https://github.com/reasonml/reason-react/), the Melange bindings +for [React](https://react.dev/). 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](./working-with-js-objects-and-values.md#bind-to-js-object). + +### 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](./advanced-js-interop.md#generate-getters-setters-and-constructors) + 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 diff --git a/docs/how-to-guides.md b/docs/how-to-guides.md index 83e41f221..a2483b93c 100644 --- a/docs/how-to-guides.md +++ b/docs/how-to-guides.md @@ -537,14 +537,15 @@ v3, but node extensions (`%bs.*`) are not. #### `@bs` attribute becomes `@u` The `@bs` attribute, used for uncurried application (see the ["Binding to -callbacks" section](./communicate-with-javascript.md#binding-to-callbacks)), -becomes `@u`. +callbacks" +section](./working-with-js-objects-and-values.md#binding-to-callbacks)), becomes +`@u`. #### `@bs.val` is gone The `@bs.val` attribute is no longer necessary, and can be removed from `external` definitions. See more information in the ["Using global functions or -values"](./communicate-with-javascript.md#using-global-functions-or-values) +values"](./working-with-js-objects-and-values.md#using-global-functions-or-values) section. #### `Dom` and `Node` are in their own libraries diff --git a/docs/language-concepts.md b/docs/language-concepts.md new file mode 100644 index 000000000..481e9af34 --- /dev/null +++ b/docs/language-concepts.md @@ -0,0 +1,453 @@ +# Language concepts + +The concepts covered in the following sections are a small subset of the OCaml +language. However, they are essential for understanding how to communicate with +JavaScript, and the features that Melange exposes to do so. + +## Extension nodes and attributes + +In order to interact with JavaScript, Melange needs to extend the language to +provide blocks that express these interactions. + +One approach could be to introduce new syntactic constructs (keywords and such) +to do so, for example: + +```text +javascript add : int -> int -> int = {|function(x,y){ + return x + y +}|} +``` + +But this would break compatibility with OCaml, which is one of the main goals of +Melange. + +Fortunately, OCaml provides mechanisms to extend its language without breaking +compatibility with the parser or the language. These mechanisms are composed by +two parts: + +- First, some syntax additions to define parts of the code that will be extended + or replaced +- Second, compile-time OCaml native programs called [PPX + rewriters](https://ocaml.org/docs/metaprogramming), that will read the syntax + additions defined above and proceed to extend or replace them + +The syntax additions come in two flavors, called [extension +nodes](https://v2.ocaml.org/manual/extensionnodes.html) and +[attributes](https://v2.ocaml.org/manual/attributes.html). + +### Extension nodes + +Extension nodes are blocks that are supposed to be replaced by a specific type +of PPX rewriters called extenders. Extension nodes use the `%` character to be +identified. Extenders will take the extension node and replace it with a valid +OCaml AST (abstract syntax tree). + +An example where Melange uses extension nodes to communicate with JavaScript is +to produce "raw" JavaScript inside a Melange program: + +```ocaml +[%%mel.raw "var a = 1; var b = 2"] +let add = [%mel.raw "a + b"] +``` +```reasonml +[%%mel.raw "var a = 1; var b = 2"]; +let add = [%mel.raw "a + b"]; +``` + +Which will generate the following JavaScript code: + +```js +var a = 1; var b = 2 +var add = a + b +``` + +The difference between one and two percentage characters is detailed in the +[OCaml documentation](https://v2.ocaml.org/manual/extensionnodes.html). + +### Attributes + +Attributes are "decorations" applied to specific parts of the code to provide +additional information. In Melange, attributes are used in two ways to enhance +the expressiveness of generating JavaScript code: either reusing existing OCaml +built-in attributes or defining new ones. + +#### Reusing OCaml attributes + +The first approach is leveraging the existing [OCaml’s built-in +attributes](https://v2.ocaml.org/manual/attributes.html#ss:builtin-attributes) +to be used for JavaScript generation. + +One prominent example of OCaml attributes that can be used in Melange projects +is the `unboxed` attribute, which optimizes the compilation of single-field +records and variants with a single tag to their raw values. This is useful when +defining type aliases that we don’t want to mix up, or when binding to +JavaScript code that uses heterogeneous collections. An example of the latter is +discussed in the [variadic function +arguments](./working-with-js-objects-and-values.md#variadic-function-arguments) +section. + +For instance: + +```ocaml +type name = + | Name of string [@@unboxed] +let student_name = Name "alice" +``` +```reasonml +[@unboxed] +type name = + | Name(string); +let student_name = Name("alice"); +``` + +Compiles into: + +```js +var student_name = "alice"; +``` + +Other OCaml pre-built attributes like `alert` or `deprecated` can be used with +Melange as well. + +#### Defining new attributes + +The second approach is introducing new attributes specifically designed for +Melange, such as the [`mel.set` +attribute](./working-with-js-objects-and-values.md#bind-to-object-properties) +used to bind to properties of JavaScript objects. The complete list of +attributes introduced by Melange can be found +[here](./attributes-and-extension-nodes.md). + +Attribute annotations can use one, two or three `@` characters depending on +their placement in the code and which kind of syntax tree node they are +annotating. More information about attributes can be found in the [dedicated +OCaml manual page](https://v2.ocaml.org/manual/attributes.html). + +Here are some samples using Melange attributes +[`mel.set`](./working-with-js-objects-and-values.md#bind-to-object-properties) +and [`mel.as`](./working-with-js-objects-and-values.md#using-ocaml-records): + +```ocaml +type document +external setTitleDom : document -> string -> unit = "title" [@@mel.set] + +type t = { + age : int; [@mel.as "a"] + name : string; [@mel.as "n"] +} +``` +```reasonml +type document; +[@mel.set] external setTitleDom: (document, string) => unit = "title"; + +type t = { + [@mel.as "a"] + age: int, + [@mel.as "n"] + name: string, +}; +``` + +To learn more about preprocessors, attributes and extension nodes, check the +[section about PPX +rewriters](https://ocaml.org/docs/metaprogramming#ppx-rewriters) in the OCaml +docs. + +## External functions + +Most of the system that Melange exposes to communicate with JavaScript is built +on top of an OCaml language construct called `external`. + +`external` is a keyword for declaring a value in OCaml that will [interface with +C code](https://v2.ocaml.org/manual/intfc.html): + +```ocaml +external my_c_function : int -> string = "someCFunctionName" +``` +```reasonml +external my_c_function: int => string = "someCFunctionName"; +``` + +It is like a `let` binding, except that the body of an external is a string. +That string has a specific meaning depending on the context. For native OCaml, +it usually refers to a C function with that name. For Melange, it refers to the +functions or values that exist in the runtime JavaScript code, and will be used +from Melange. + +In Melange, externals can be used to [bind to global JavaScript +objects](./working-with-js-objects-and-values.md#using-global-functions-or-values). +They can also be decorated with certain `[@mel.xxx]` attributes to facilitate +the creation of bindings in specific scenarios. Each one of the [available +attributes](./attributes-and-extension-nodes.md#attributes) will be further +explained in the next sections. + +Once declared, one can use an `external` as a normal value. Melange external +functions are turned into the expected JavaScript values, inlined into their +callers during compilation, and completely erased afterwards. They don’t appear +in the JavaScript output, so there are no costs on bundling size. + +**Note**: it is recommended to use external functions and the `[@mel.xxx]` +attributes in the interface files as well, as this allows some optimizations +where the resulting JavaScript values can be directly inlined at the call sites. + +### Special identity external + +One external worth mentioning is the following one: + +```ocaml +type foo = string +type bar = int +external danger_zone : foo -> bar = "%identity" +``` +```reasonml +type foo = string; +type bar = int; +external danger_zone: foo => bar = "%identity"; +``` + +This is a final escape hatch which does nothing but convert from the type `foo` +to `bar`. In the following sections, if you ever fail to write an `external`, +you can fall back to using this one. But try not to. + +## Abstract types + +In the subsequent sections, you will come across examples of bindings where a +type is defined without being assigned to a value. Here is an example: + +```ocaml +type document +``` +```reasonml +type document; +``` + +These types are referred to as "abstract types" and are commonly used together +with external functions that define operations over values when communicating +with JavaScript. + +Abstract types enable defining types for specific values originating from +JavaScript while omitting unnecessary details. An illustration is the `document` +type mentioned earlier, which has several +[properties](https://developer.mozilla.org/en-US/docs/Web/API/Document). By +using abstract types, one can focus solely on the required aspects of the +`document` value that the Melange program requires, rather than defining all its +properties. Consider the following example: + +```ocaml +type document + +external document : document = "document" +external set_title : document -> string -> unit = "title" [@@mel.set] +``` +```reasonml +type document; + +external document: document = "document"; +[@mel.set] external set_title: (document, string) => unit = "title"; +``` + +Subsequent sections delve into the details about the +[`mel.set`](./working-with-js-objects-and-values.md#bind-to-object-properties) +attribute and [how to bind to global +values](./working-with-js-objects-and-values.md#using-global-functions-or-values) +like `document`. + +For a comprehensive understanding of abstract types and their usefulness, refer +to the "Encapsulation" section of the [OCaml Cornell +textbook](https://cs3110.github.io/textbook/chapters/modules/encapsulation.html). + +## Pipe operators + +There are two pipe operators available in Melange: + +- A _pipe last_ operator `|>`, available [in + OCaml](https://v2.ocaml.org/api/Stdlib.html#1_Compositionoperators) and + inherited in Melange +- A _pipe first_ operator \|.\-\>, available exclusively in Melange + +Let’s see the differences between the two. + +### Pipe last + +Since version 4.01, OCaml includes a reverse application or "pipe" (`|>`) +operator, an infix operator that applies the result from the previous expression +the next function. As a backend for OCaml, Melange inherits this operator. + +The pipe operator could be implemented like this (the real implementation is a +bit +[different](https://github.com/ocaml/ocaml/blob/d9547617e8b14119beacafaa2546cbebfac1bfe5/stdlib/stdlib.ml#L48)): + +```ocaml +let ( |> ) f g = g f +``` +```reasonml +let (|>) = (f, g) => g(f); +``` + +This operator is useful when multiple functions are applied to some value in +sequence, with the output of each function becoming the input of the next (a +_pipeline_). + +For example, assuming we have a function `square` defined as: + +```ocaml +let square x = x * x +``` +```reasonml +let square = x => x * x; +``` + +We are using it like: + +```ocaml +let ten = succ (square 3) +``` +```reasonml +let ten = succ(square(3)); +``` + +The pipe operator allows to write the computation for `ten` in left-to-right +order, as [it has left +associativity](https://v2.ocaml.org/manual/expr.html#ss:precedence-and-associativity): + +```ocaml +let ten = 3 |> square |> succ +``` +```reasonml +let ten = 3 |> square |> succ; +``` + +When working with functions that can take multiple arguments, the pipe operator +works best when the functions take the data we are processing as the last +argument. For example: + +```ocaml +let sum = List.fold_left ( + ) 0 + +let sum_sq = + [ 1; 2; 3 ] + |> List.map square (* [1; 4; 9] *) + |> sum (* 1 + 4 + 9 *) +``` +```reasonml +let sum = List.fold_left((+), 0); + +let sum_sq = + [1, 2, 3] + |> List.map(square) /* [1; 4; 9] */ + |> sum; /* 1 + 4 + 9 */ +``` + +The above example can be written concisely because the `List.map` function in +the [OCaml standard library](https://v2.ocaml.org/api/Stdlib.List.html) takes +the list as the second argument. This convention is sometimes referred to as +"data-last", and is widely adopted in the OCaml ecosystem. Data-last and the +pipe operator `|>` work great with currying, so they are a great fit for the +language. + +However, there are some limitations when using data-last when it comes to error +handling. In the given example, if we mistakenly used the wrong function: + +```ocaml +let sum_sq = + [ 1; 2; 3 ] + |> List.map String.cat + |> sum +``` +```reasonml +let sum_sq = [1, 2, 3] |> List.map(String.cat) |> sum; +``` + +The compiler would rightfully raise an error: + +
+
4 |   [ 1; 2; 3 ]
+          ^
+  Error: This expression has type int but an expression was expected of type
+          string
+
1 |   [ 1, 2, 3 ]
+          ^
+  Error: This expression has type int but an expression was expected of type
+          string
+
+ +Note that instead of telling us that we are passing the wrong function in +`List.map` (`String.cat`), the error points to the list itself. This behavior +aligns with the way type inference works, as the compiler infers types from left +to right. Since `[ 1; 2; 3 ] |> List.map String.cat` is equivalent to `List.map +String.cat [ 1; 2; 3 ]`, the type mismatch is detected when the list is type +checked, after `String.cat` has been processed. + +With the goal of addressing this kind of limitations, Melange introduces the +pipe first operator \|.\-\>. + +### Pipe first + +To overcome the constraints mentioned above, Melange introduces the pipe first +operator \|.\-\>. + +As its name suggests, the pipe first operator is better suited for functions +where the data is passed as the first argument. + +The functions in the Belt libraryBelt library included with Melange +have been designed with the data-first convention in mind, so they work best +with the pipe first operator. + +For example, we can rewrite the example above using `Belt.List.map` and the pipe +first operator: + +```ocaml +let sum_sq = + [ 1; 2; 3 ] + |. Belt.List.map square + |. sum +``` +```reasonml +let sum_sq = [1, 2, 3]->(Belt.List.map(square))->sum; +``` + +We can see the difference on the error we get if the wrong function is passed to +`Belt.List.map`: + +```ocaml +let sum_sq = + [ 1; 2; 3 ] + |. Belt.List.map String.cat + |. sum +``` +```reasonml +let sum_sq = [1, 2, 3]->(Belt.List.map(String.cat))->sum; +``` + +The compiler will show this error message: + +
+
4 |   |. Belt.List.map String.cat
+                       ^^^^^^^^^^
+Error: This expression has type string -> string -> string
+       but an expression was expected of type int -> 'a
+       Type string is not compatible with type int
+
2 | let sum_sq = [1, 2, 3]->(Belt.List.map(String.cat))->sum;
+                                           ^^^^^^^^^^
+Error: This expression has type string -> string -> string
+       but an expression was expected of type int -> 'a
+       Type string is not compatible with type int
+
+ +The error points now to the function passed to `Belt.List.map`, which is more +natural with the way the code is being written. + +Melange supports writing bindings to JavaScript using any of the two +conventions, data-first or data-last, as shown in the ["Chaining" +section](./working-with-js-objects-and-values.md#chaining). + +For further details about the differences between the two operators, the +data-first and data-last conventions and the trade-offs between them, one can +refer to [this related blog +post](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/). diff --git a/docs/melange-for-x-developers.md b/docs/melange-for-x-developers.md index b5512209a..8819e9dba 100644 --- a/docs/melange-for-x-developers.md +++ b/docs/melange-for-x-developers.md @@ -349,8 +349,8 @@ TypeScript, as mentioned in [its handbook](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness), may sacrifice soundness for practicality when needed. In contrast, OCaml implementations provide unsound methods like [the `identity` -primitive](communicate-with-javascript.md#special-identity-external) but they -are generally discouraged and rarely used. The OCaml community places a strong +primitive](./language-concepts.md#special-identity-external) but they are +generally discouraged and rarely used. The OCaml community places a strong emphasis on maintaining soundness and prefers safer alternatives to ensure code correctness. @@ -492,8 +492,8 @@ starts the conversion process earlier in the compiler pipeline, as it transforms the compiler lambda representation into JavaScript. Js\_of\_ocaml is a project with years of development and evolution behind it, -while Melange [appearance](rationale.md#a-bit-of-history) is relatively recent -in comparison. +while Melange's [appearance](./rationale.md#a-bit-of-history) is relatively +recent in comparison. These aspects translate into different trade-offs. Compared to Js\_of\_ocaml: @@ -534,7 +534,7 @@ of its characteristics: JavaScript file. - The libraries provided by ReScript (Belt and Js) are available in Melange too. - The mechanisms provided for [communicating with JavaScript - code](communicate-with-javascript.md) are mostly the same. + code](./communicate-with-javascript.md) are mostly the same. However, one of Melange’s goals is to maximize compatibility with the OCaml ecosystem. This goal translates into fundamental differences in how Melange and @@ -545,14 +545,14 @@ ReScript function from the perspective of both library authors and users. ReScript projects rely exclusively on npm for all packages they depend on. Melange projects, on the other hand, will use opam for native packages, and npm for JavaScript ones. Melange package management is explained in detail in [the -dedicated section](package-management.md). +dedicated section](./package-management.md). ### Build system ReScript has its own build system, originally based on Ninja. Melange defers to [Dune](https://dune.build/) for build orchestration, as it is -explained in detail in [the corresponding section](build-system.md). By +explained in detail in [the corresponding section](./build-system.md). By integrating with Dune, Melange can benefit from the multiple features provided. One of the most useful features is first-class supports for monorepos. But there are multiple others, like virtual libraries, watch mode, or integrations with diff --git a/docs/working-with-js-objects-and-values.md b/docs/working-with-js-objects-and-values.md new file mode 100644 index 000000000..9db48d29b --- /dev/null +++ b/docs/working-with-js-objects-and-values.md @@ -0,0 +1,1508 @@ +# Working with JavaScript objects and values + +## Bind to JavaScript objects + +JavaScript objects are used in a variety of use cases: + +- As a fixed shape + [record](https://en.wikipedia.org/wiki/Record_\(computer_science\)). +- As a map or dictionary. +- As a class. +- As a module to import/export. + +Melange separates the binding methods for JavaScript objects based on these four +use cases. This section documents the first three. Binding to JavaScript module +objects is described in the ["Using functions from other JavaScript +modules"](#using-functions-from-other-javascript-modules) section. + + + +### Objects with static shape (record-like) + +#### Using OCaml records + +If your JavaScript object has fixed fields, then it’s conceptually like an +[OCaml +record](https://v2.ocaml.org/manual/coreexamples.html#s%3Atut-recvariants). +Since Melange compiles records into JavaScript objects, the most common way to +bind to JavaScript objects is using records. + +```ocaml +type person = { + name : string; + friends : string array; + age : int; +} + +external john : person = "john" [@@mel.module "MySchool"] +let john_name = john.name +``` +```reasonml +type person = { + name: string, + friends: array(string), + age: int, +}; + +[@mel.module "MySchool"] external john: person = "john"; +let john_name = john.name; +``` + +This is the generated JavaScript: + +```js +var MySchool = require("MySchool"); + +var john_name = MySchool.john.name; +``` + +External functions are documented in [a previous +section](./language-concepts.md#external-functions). The `mel.module` attribute +is documented [here](#using-functions-from-other-javascript-modules). + +If you want or need to use different field names on the Melange and the +JavaScript sides, you can use the `mel.as` decorator: + +```ocaml +type action = { + type_ : string [@mel.as "type"] +} + +let action = { type_ = "ADD_USER" } +``` +```reasonml +type action = { + [@mel.as "type"] + type_: string, +}; + +let action = {type_: "ADD_USER"}; +``` + +Which generates the JavaScript code: + +```js +var action = { + type: "ADD_USER" +}; +``` + +This is useful to map to JavaScript attribute names that cannot be expressed in +Melange, for example, where the JavaScript name we want to generate is a +[reserved keyword](https://v2.ocaml.org/manual/lex.html#sss:keywords). + +It is also possible to map a Melange record to a JavaScript array by passing +indices to the `mel.as` decorator: + +```ocaml +type t = { + foo : int; [@mel.as "0"] + bar : string; [@mel.as "1"] +} + +let value = { foo = 7; bar = "baz" } +``` +```reasonml +type t = { + [@mel.as "0"] + foo: int, + [@mel.as "1"] + bar: string, +}; + +let value = {foo: 7, bar: "baz"}; +``` + +And its JavaScript generated code: + +```js +var value = [ + 7, + "baz" +]; +``` + +#### Using `Js.t` objects + +Alternatively to records, Melange offers another type that can be used to +produce JavaScript objects. This type is 'a +Js.tJs.t('a), where `'a` is an [OCaml +object](https://v2.ocaml.org/manual/objectexamples.html). + +The advantage of objects versus records is that no type declaration is needed in +advance, which can be helpful for prototyping or quickly generating JavaScript +object literals. + +Melange provides some ways to create `Js.t` object values, as well as accessing +the properties inside them. To create values, the `[%mel.obj]` extension is +used, and the `##` infix operator allows to read from the object properties: + +```ocaml +let john = [%mel.obj { name = "john"; age = 99 }] +let t = john##name +``` +```reasonml +let john = {"name": "john", "age": 99}; +let t = john##name; +``` + +Which generates: + +```js +var john = { + name: "john", + age: 99 +}; + +var t = john.name; +``` + +Note that object types allow for some flexibility that the record types do not +have. For example, an object type can be coerced to another with fewer values or +methods, while it is impossible to coerce a record type to another one with +fewer fields. So different object types that share some methods can be mixed in +a data structure where only their common methods are visible. + +To give an example, one can create a function that operates in all the object +types that include a field `name` that is of type string, e.g.: + +```ocaml +let name_extended obj = obj##name ^ " wayne" + +let one = name_extended [%mel.obj { name = "john"; age = 99 }] +let two = name_extended [%mel.obj { name = "jane"; address = "1 infinite loop" }] +``` +```reasonml +let name_extended = obj => obj##name ++ " wayne"; + +let one = name_extended({"name": "john", "age": 99}); +let two = name_extended({"name": "jane", "address": "1 infinite loop"}); +``` + +To read more about objects and polymorphism we recommend checking the [OCaml +docs](https://ocaml.org/docs/objects) or the [OCaml +manual](https://v2.ocaml.org/manual/objectexamples.html). + +#### Using external functions + +We have already explored one approach for creating JavaScript object literals by +using [`Js.t` values and the `mel.obj` extension](#using-js-t-objects). + +Melange additionally offers the `mel.obj` attribute, which can be used in +combination with external functions to create JavaScript objects. When these +functions are called, they generate objects with fields corresponding to the +labeled arguments of the function. + +If any of these labeled arguments are defined as optional and omitted during +function application, the resulting JavaScript object will exclude the +corresponding fields. This allows to create runtime objects and control whether +optional keys are emitted at runtime. + +For example, assuming we need to bind to a JavaScript object like this: + +```js +var homeRoute = { + type: "GET", + path: "/", + action: () => console.log("Home"), + // options: ... +}; +``` + +The first three fields are required and the `options` field is optional. You can +declare a binding function like: + +```ocaml +external route : + _type:string -> + path:string -> + action:(string list -> unit) -> + ?options:< .. > -> + unit -> + _ = "" + [@@mel.obj] +``` +```reasonml +[@mel.obj] +external route: + ( + ~_type: string, + ~path: string, + ~action: list(string) => unit, + ~options: {..}=?, + unit + ) => + _; +``` + +Note that the empty string at the end of the function is used to make it +syntactically valid. The value of this string is ignored by the compiler. + +Since there is an optional argument `options`, an additional unlabeled argument +of type `unit` is included after it. It allows to omit the optional argument on +function application. More information about labeled optional arguments can be +found in the [OCaml +manual](https://v2.ocaml.org/manual/lablexamples.html#s:optional-arguments). + +The return type of the function should be left unspecified using the wildcard +type `_`. Melange will automatically infer the type of the resulting JavaScript +object. + +In the route function, the `_type` argument starts with an underscore. When +binding to JavaScript objects with fields that are reserved keywords in OCaml, +Melange allows the use of an underscore prefix for the labeled arguments. The +resulting JavaScript object will have the underscore removed from the field +names. This is only required for the `mel.obj` attribute, while for other cases, +the `mel.as` attribute can be used to rename fields. + +If we call the function like this: + +```ocaml +let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () +``` +```reasonml +let homeRoute = + route(~_type="GET", ~path="/", ~action=_ => Js.log("Home"), ()); +``` + +We get the following JavaScript, which does not include the `options` field +since its argument wasn’t present: + +```javascript +var homeRoute = { + type: "GET", + path: "/", + action: (function (param) { + console.log("Home"); + }) +}; +``` + +#### Bind to object properties + +If you need to bind only to the property of a JavaScript object, you can use +`mel.get` and `mel.set` to access it using the dot notation `.`: + +```ocaml +(* Abstract type for the `document` value *) +type document + +external document : document = "document" + +external set_title : document -> string -> unit = "title" [@@mel.set] +external get_title : document -> string = "title" [@@mel.get] + +let current = get_title document +let () = set_title document "melange" +``` +```reasonml +/* Abstract type for the `document` value */ +type document; + +external document: document = "document"; + +[@mel.set] external set_title: (document, string) => unit = "title"; +[@mel.get] external get_title: document => string = "title"; + +let current = get_title(document); +let () = set_title(document, "melange"); +``` + +This generates: + +```javascript +var current = document.title; +document.title = "melange"; +``` + +Alternatively, if some dynamism is required on the way the property is accessed, +you can use `mel.get_index` and `mel.set_index` to access it using the bracket +notation `[]`: + +```ocaml +type t +external create : int -> t = "Int32Array" [@@mel.new] +external get : t -> int -> int = "" [@@mel.get_index] +external set : t -> int -> int -> unit = "" [@@mel.set_index] + +let () = + let i32arr = (create 3) in + set i32arr 0 42; + Js.log (get i32arr 0) +``` +```reasonml +type t; +[@mel.new] external create: int => t = "Int32Array"; +[@mel.get_index] external get: (t, int) => int; +[@mel.set_index] external set: (t, int, int) => unit; + +let () = { + let i32arr = create(3); + set(i32arr, 0, 42); + Js.log(get(i32arr, 0)); +}; +``` + +Which generates: + +```js +var i32arr = new Int32Array(3); +i32arr[0] = 42; +console.log(i32arr[0]); +``` + +### Objects with dynamic shape (dictionary-like) + +Sometimes JavaScript objects are used as dictionaries. In these cases: + +- All values stored in the object belong to the same type +- Key-value pairs can be added or removed at runtime + +For this particular use case of JavaScript objects, Melange exposes a specific +type `Js.Dict.t`. The values and functions to work with values of this type are +defined in the Js.Dict moduleJs.Dict module, with operations +like `get`, `set`, etc. + +Values of the type `Js.Dict.t` compile to JavaScript objects. + +### JavaScript classes + +JavaScript classes are special kinds of objects. To interact with classes, +Melange exposes `mel.new` to emulate e.g. `new Date()`: + +```ocaml +type t +external create_date : unit -> t = "Date" [@@mel.new] +let date = create_date () +``` +```reasonml +type t; +[@mel.new] external create_date: unit => t = "Date"; +let date = create_date(); +``` + +Which generates: + +```js +var date = new Date(); +``` + +You can chain `mel.new` and `mel.module` if the JavaScript class you want to +work with is in a separate JavaScript module: + +```ocaml +type t +external book : unit -> t = "Book" [@@mel.new] [@@mel.module] +let myBook = book () +``` +```reasonml +type t; +[@mel.new] [@mel.module] external book: unit => t = "Book"; +let myBook = book(); +``` + +Which generates: + +```js +var Book = require("Book"); +var myBook = new Book(); +``` + +## Bind to JavaScript functions or values + +### Using global functions or values + +Binding to a JavaScript function available globally makes use of `external`, +like with objects. But unlike objects, there is no need to add any attributes: + +```ocaml +(* Abstract type for `timeoutId` *) +type timeoutId +external setTimeout : (unit -> unit) -> int -> timeoutId = "setTimeout" +external clearTimeout : timeoutId -> unit = "clearTimeout" + +let id = setTimeout (fun () -> Js.log "hello") 100 +let () = clearTimeout id +``` +```reasonml +/* Abstract type for `timeoutId` */ +type timeoutId; +external setTimeout: (unit => unit, int) => timeoutId = "setTimeout"; +external clearTimeout: timeoutId => unit = "clearTimeout"; + +let id = setTimeout(() => Js.log("hello"), 100); +let () = clearTimeout(id); +``` + +> **_NOTE:_** The bindings to `setTimeout` and `clearTimeout` are shown here for +> learning purposes, but they are already available in the target="_self" href="./api/ml/melange/Js/Global">Js.Global +> module href="./api/re/melange/Js/Global">Js.Global module. + +Generates: + +```javascript +var id = setTimeout(function (param) { + console.log("hello"); +}, 100); + +clearTimeout(id); +``` + +Global bindings can also be applied to values: + +```ocaml +(* Abstract type for `document` *) +type document + +external document : document = "document" +let document = document +``` +```reasonml +/* Abstract type for `document` */ +type document; + +external document: document = "document"; +let document = document; +``` + +Which generates: + +```javascript +var doc = document; +``` + +### Using functions from other JavaScript modules + +`mel.module` allows to bind to values that belong to another JavaScript module. +It accepts a string with the name of the module, or the relative path to it. + +```ocaml +external dirname : string -> string = "dirname" [@@mel.module "path"] +let root = dirname "/User/github" +``` +```reasonml +[@mel.module "path"] external dirname: string => string = "dirname"; +let root = dirname("/User/github"); +``` + +Generates: + +```js +var Path = require("path"); +var root = Path.dirname("/User/github"); +``` + +### Binding to properties inside a module or global + +For cases when we need to create bindings for a property within a module or a +global JavaScript object, Melange provides the `mel.scope` attribute. + +For example, if we want to write some bindings for a specific property +`commands` from [the `vscode` +package](https://code.visualstudio.com/api/references/vscode-api#commands), we +can do: + +```ocaml +type param +external executeCommands : string -> param array -> unit = "" + [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] + +let f a b c = executeCommands "hi" [| a; b; c |] +``` +```reasonml +type param; +[@mel.scope "commands"] [@mel.module "vscode"] [@mel.variadic] +external executeCommands: (string, array(param)) => unit; + +let f = (a, b, c) => executeCommands("hi", [|a, b, c|]); +``` + +Which compiles to: + +```javascript +var Vscode = require("vscode"); + +function f(a, b, c) { + Vscode.commands.executeCommands("hi", a, b, c); +} +``` + +The `mel.scope` attribute can take multiple arguments as payload, in case we +want to reach deeper into the object from the module we are importing. + +For example: + +```ocaml +type t + +external back : t = "back" + [@@mel.module "expo-camera"] [@@mel.scope "Camera", "Constants", "Type"] + +let camera_type_back = back +``` +```reasonml +type t; + +[@mel.module "expo-camera"] [@mel.scope ("Camera", "Constants", "Type")] +external back: t = "back"; + +let camera_type_back = back; +``` + +Which generates: + +```javascript +var ExpoCamera = require("expo-camera"); + +var camera_type_back = ExpoCamera.Camera.Constants.Type.back; +``` + +It can be used without `mel.module`, to created scoped bindings to global +values: + +```ocaml +external imul : int -> int -> int = "imul" [@@mel.scope "Math"] + +let res = imul 1 2 +``` +```reasonml +[@mel.scope "Math"] external imul: (int, int) => int = "imul"; + +let res = imul(1, 2); +``` + +Which produces: + +```javascript +var res = Math.imul(1, 2); +``` + +Or it can be used together with `mel.new`: + +```ocaml +type t + +external create : unit -> t = "GUI" + [@@mel.new] [@@mel.scope "default"] [@@mel.module "dat.gui"] + +let gui = create () +``` +```reasonml +type t; + +[@mel.new] [@mel.scope "default"] [@mel.module "dat.gui"] +external create: unit => t = "GUI"; + +let gui = create(); +``` + +Which generates: + + +```javascript +var DatGui = require("dat.gui"); + +var gui = new (DatGui.default.GUI)(); +``` + +### Labeled arguments + +OCaml has [labeled arguments](https://v2.ocaml.org/manual/lablexamples.html), +which can also be optional, and work with `external` as well. + +Labeled arguments can be useful to provide more information about the arguments +of a JavaScript function that is called from Melange. + +Let’s say we have the following JavaScript function, that we want to call from +Melange: + +```js +// MyGame.js + +function draw(x, y, border) { + // let’s assume `border` is optional and defaults to false +} +draw(10, 20) +draw(20, 20, true) +``` + +When writing Melange bindings, we can add labeled arguments to make things more +clear: + +```ocaml +external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" + [@@mel.module "MyGame"] + +let () = draw ~x:10 ~y:20 ~border:true () +let () = draw ~x:10 ~y:20 () +``` +```reasonml +[@mel.module "MyGame"] +external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"; + +let () = draw(~x=10, ~y=20, ~border=true, ()); +let () = draw(~x=10, ~y=20, ()); +``` + +Generates: + +```js +var MyGame = require("MyGame"); + +MyGame.draw(10, 20, true); +MyGame.draw(10, 20, undefined); +``` + +The generated JavaScript function is the same, but now the usage in Melange is +much clearer. + +**Note**: in this particular case, a final param of type unit, `()` must be +added after `border`, since `border` is an optional argument at the last +position. Not having the last param `unit` would lead to a warning, which is +explained in detail [in the OCaml +documentation](https://ocaml.org/docs/labels#warning-this-optional-argument-cannot-be-erased). + +Note that you can freely reorder the labeled arguments when applying the +function on the Melange side. The generated code will maintain the original +order that was used when declaring the function: + +```ocaml +external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" + [@@mel.module "MyGame"] +let () = draw ~x:10 ~y:20 () +let () = draw ~y:20 ~x:10 () +``` +```reasonml +[@mel.module "MyGame"] +external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"; +let () = draw(~x=10, ~y=20, ()); +let () = draw(~y=20, ~x=10, ()); +``` + +Generates: + +```js +var MyGame = require("MyGame"); + +MyGame.draw(10, 20, undefined); +MyGame.draw(10, 20, undefined); +``` + +### Calling an object method + +If we need to call a JavaScript method, Melange provides the attribute +`mel.send`. + +> In the following snippets, we will be referring to a type `Dom.element`, which +> is provided within the library `melange.dom`. You can add it to your project +> by including `(libraries melange.dom)` to your `dune` file: + +```ocaml +(* Abstract type for the `document` global *) +type document + +external document : document = "document" +external get_by_id : document -> string -> Dom.element = "getElementById" + [@@mel.send] + +let el = get_by_id document "my-id" +``` +```reasonml +/* Abstract type for the `document` global */ +type document; + +external document: document = "document"; +[@mel.send] +external get_by_id: (document, string) => Dom.element = "getElementById"; + +let el = get_by_id(document, "my-id"); +``` + +Generates: + +```js +var el = document.getElementById("my-id"); +``` + +When using `mel.send`, the first argument will be the object that holds the +property with the function we want to call. This combines well with the pipe +first operator \|.\-\>, see the ["Chaining"](#chaining) section +below. + +If we want to design our bindings to be used with OCaml pipe last operator `|>`, +there is an alternate `mel.send.pipe` attribute. Let’s rewrite the example above +using it: + +```ocaml +(* Abstract type for the `document` global *) +type document + +external document : document = "document" +external get_by_id : string -> Dom.element = "getElementById" + [@@mel.send.pipe: document] + +let el = get_by_id "my-id" document +``` +```reasonml +/* Abstract type for the `document` global */ +type document; + +external document: document = "document"; +[@mel.send.pipe: document] +external get_by_id: string => Dom.element = "getElementById"; + +let el = get_by_id("my-id", document); +``` + +Generates the same code as `mel.send`: + +```js +var el = document.getElementById("my-id"); +``` + +#### Chaining + +It is common to find this kind of API in JavaScript: `foo().bar().baz()`. This +kind of API can be designed with Melange externals. Depending on which +convention we want to use, there are two attributes available: + +- For a data-first convention, the `mel.send` attribute, in combination with + [the pipe first operator](./language-concepts.md#pipe-first) \|.\-\> +- For a data-last convention, the `mel.send.pipe` attribute, in combination with + OCaml [pipe last operator](./language-concepts.md#pipe-last) `|>`. + +Let’s see first an example of chaining using data-first convention with the pipe +first operator \|.\-\>: + +```ocaml +(* Abstract type for the `document` global *) +type document + +external document : document = "document" +external get_by_id : document -> string -> Dom.element = "getElementById" + [@@mel.send] +external get_by_classname : Dom.element -> string -> Dom.element + = "getElementsByClassName" + [@@mel.send] + +let el = document |. get_by_id "my-id" |. get_by_classname "my-class" +``` +```reasonml +/* Abstract type for the `document` global */ +type document; + +external document: document = "document"; +[@mel.send] +external get_by_id: (document, string) => Dom.element = "getElementById"; +[@mel.send] +external get_by_classname: (Dom.element, string) => Dom.element = + "getElementsByClassName"; + +let el = document->(get_by_id("my-id"))->(get_by_classname("my-class")); +``` + +Will generate: + +```javascript +var el = document.getElementById("my-id").getElementsByClassName("my-class"); +``` + +Now with pipe last operator `|>`: + +```ocaml +(* Abstract type for the `document` global *) +type document + +external document : document = "document" +external get_by_id : string -> Dom.element = "getElementById" + [@@mel.send.pipe: document] +external get_by_classname : string -> Dom.element = "getElementsByClassName" + [@@mel.send.pipe: Dom.element] + +let el = document |> get_by_id "my-id" |> get_by_classname "my-class" +``` +```reasonml +/* Abstract type for the `document` global */ +type document; + +external document: document = "document"; +[@mel.send.pipe: document] +external get_by_id: string => Dom.element = "getElementById"; +[@mel.send.pipe: Dom.element] +external get_by_classname: string => Dom.element = "getElementsByClassName"; + +let el = document |> get_by_id("my-id") |> get_by_classname("my-class"); +``` + +Will generate the same JavaScript as the pipe first version: + +```javascript +var el = document.getElementById("my-id").getElementsByClassName("my-class"); +``` + +### Variadic function arguments + +Sometimes JavaScript functions take an arbitrary amount of arguments. For these +cases, Melange provides the `mel.variadic` attribute, which can be attached to +the `external` declaration. However, there is one caveat: all the variadic +arguments need to belong to the same type. + +```ocaml +external join : string array -> string = "join" + [@@mel.module "path"] [@@mel.variadic] +let v = join [| "a"; "b" |] +``` +```reasonml +[@mel.module "path"] [@mel.variadic] +external join: array(string) => string = "join"; +let v = join([|"a", "b"|]); +``` + +Generates: + +```js +var Path = require("path"); +var v = Path.join("a", "b"); +``` + +If more dynamism is needed, there is a way to inject elements with different +types in the array and still have Melange compile to JavaScript values that are +not wrapped using the OCaml +[`unboxed`](https://v2.ocaml.org/manual/attributes.html) attribute, which was +mentioned [in the OCaml attributes +section](/language-concepts#reusing-ocaml-attributes): + +```ocaml +type hide = Hide : 'a -> hide [@@unboxed] + +external join : hide array -> string = "join" [@@mel.module "path"] [@@mel.variadic] + +let v = join [| Hide "a"; Hide 2 |] +``` +```reasonml +[@unboxed] +type hide = + | Hide('a): hide; + +[@mel.module "path"] [@mel.variadic] +external join: array(hide) => string = "join"; + +let v = join([|Hide("a"), Hide(2)|]); +``` + +Compiles to: + +```javascript +var Path = require("path"); + +var v = Path.join("a", 2); +``` + +### Bind to a polymorphic function + +Some JavaScript libraries will define functions where the arguments can vary on +both type and shape. There are two approaches to bind to those, depending on how +dynamic they are. + +#### Approach 1: Multiple external functions + +If it is possible to enumerate the many forms an overloaded JavaScript function +can take, a flexible approach is to bind to each form individually: + +```ocaml +external drawCat : unit -> unit = "draw" [@@mel.module "MyGame"] +external drawDog : giveName:string -> unit = "draw" [@@mel.module "MyGame"] +external draw : string -> useRandomAnimal:bool -> unit = "draw" + [@@mel.module "MyGame"] +``` +```reasonml +[@mel.module "MyGame"] external drawCat: unit => unit = "draw"; +[@mel.module "MyGame"] external drawDog: (~giveName: string) => unit = "draw"; +[@mel.module "MyGame"] +external draw: (string, ~useRandomAnimal: bool) => unit = "draw"; +``` + +Note how all three externals bind to the same JavaScript function, `draw`. + +#### Approach 2: Polymorphic variant + `mel.unwrap` + +In some cases, the function has a constant number of arguments but the type of +the argument can vary. For cases like this, we can model the argument as a +variant and use the `mel.unwrap` attribute in the external. + +Let’s say we want to bind to the following JavaScript function: + +```js +function padLeft(value, padding) { + if (typeof padding === "number") { + return Array(padding + 1).join(" ") + value; + } + if (typeof padding === "string") { + return padding + value; + } + throw new Error(`Expected string or number, got '${padding}'.`); +} +``` + +As the `padding` argument can be either a number or a string, we can use +`mel.unwrap` to define it. It is important to note that `mel.unwrap` imposes +certain requirements on the type it is applied to: + +- It needs to be a [polymorphic + variant](https://v2.ocaml.org/manual/polyvariant.html) +- Its definition needs to be inlined +- Each variant tag needs to have an argument +- The variant type can not be opened (can’t use `>`) + +```ocaml +external padLeft: + string + -> ([ `Str of string + | `Int of int + ] [@mel.unwrap]) + -> string + = "padLeft" + +let _ = padLeft "Hello World" (`Int 4) +let _ = padLeft "Hello World" (`Str "Message from Melange: ") +``` +```reasonml +external padLeft: + (string, [@mel.unwrap] [ | `Str(string) | `Int(int)]) => string = + "padLeft"; + +let _ = padLeft("Hello World", `Int(4)); +let _ = padLeft("Hello World", `Str("Message from Melange: ")); +``` + +Which produces the following JavaScript: + +```js +padLeft("Hello World", 4); +padLeft("Hello World", "Message from Melange: "); +``` + +As we saw in the [Non-shared data +types](./data-types-and-runtime-rep.md#non-shared-data-types) section, we should +rather avoid passing variants directly to the JavaScript side. By using +`mel.unwrap` we get the best of both worlds: from Melange we can use variants, +while JavaScript gets the raw values inside them. + +### Using polymorphic variants to bind to enums + +Some JavaScript APIs take a limited subset of values as input. For example, +Node’s `fs.readFileSync` second argument can only take a few given string +values: `"ascii"`, `"utf8"`, etc. Some other functions can take values from a +few given integers, like the `createStatusBarItem` function in VS Code API, +which can take an `alignment` parameter that can only be [`1` or +`2`](https://github.com/Microsoft/vscode/blob/2362ec665c84a1519162b50c36ed4f29d1e20f62/src/vs/vscode.d.ts#L4098-L4109). + +One could still type these parameters as just `string` or `int`, but this would +not prevent consumers of the external function from calling it using values that +are unsupported by the JavaScript function. Let’s see how we can use polymorphic +variants to avoid runtime errors. + +If the values are strings, we can use the `mel.string` attribute: + +```ocaml +external read_file_sync : + name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" + [@@mel.module "fs"] + +let _ = read_file_sync ~name:"xx.txt" `ascii +``` +```reasonml +[@mel.module "fs"] +external read_file_sync: + (~name: string, [@mel.string] [ | `utf8 | `ascii]) => string = + "readFileSync"; + +let _ = read_file_sync(~name="xx.txt", `ascii); +``` + +Which generates: + +```js +var Fs = require("fs"); +Fs.readFileSync("xx.txt", "ascii"); +``` + +This technique can be combined with the `mel.as` attribute to modify the strings +produced from the polymorphic variant values. For example: + +```ocaml +type document +type style + +external document : document = "document" +external get_by_id : document -> string -> Dom.element = "getElementById" +[@@mel.send] +external style : Dom.element -> style = "style" [@@mel.get] +external transition_timing_function : + style -> + ([ `ease + | `easeIn [@mel.as "ease-in"] + | `easeOut [@mel.as "ease-out"] + | `easeInOut [@mel.as "ease-in-out"] + | `linear ] + [@mel.string]) -> + unit = "transitionTimingFunction" +[@@mel.set] + +let element_style = style (get_by_id document "my-id") +let () = transition_timing_function element_style `easeIn +``` +```reasonml +type document; +type style; + +external document: document = "document"; +[@mel.send] +external get_by_id: (document, string) => Dom.element = "getElementById"; +[@mel.get] external style: Dom.element => style = "style"; +[@mel.set] +external transition_timing_function: + ( + style, + [@mel.string] [ + | `ease + | [@mel.as "ease-in"] `easeIn + | [@mel.as "ease-out"] `easeOut + | [@mel.as "ease-in-out"] `easeInOut + | `linear + ] + ) => + unit = + "transitionTimingFunction"; + +let element_style = style(get_by_id(document, "my-id")); +let () = transition_timing_function(element_style, `easeIn); +``` + +This will generate: + +```javascript +var element_style = document.getElementById("my-id").style; + +element_style.transitionTimingFunction = "ease-in"; +``` + +Aside from producing string values, Melange also offers `mel.int` to produce +integer values. `mel.int` can also be combined with `mel.as`: + +```ocaml +external test_int_type : + ([ `on_closed | `on_open [@mel.as 20] | `in_bin ][@mel.int]) -> int + = "testIntType" + +let value = test_int_type `on_open +``` +```reasonml +external test_int_type: + ([@mel.int] [ | `on_closed | [@mel.as 20] `on_open | `in_bin]) => int = + "testIntType"; + +let value = test_int_type(`on_open); +``` + +In this example, `on_closed` will be encoded as 0, `on_open` will be 20 due to +the attribute `mel.as` and `in_bin` will be 21, because if no `mel.as` +annotation is provided for a variant tag, the compiler continues assigning +values counting up from the previous one. + +This code generates: + +```js +var value = testIntType(20); +``` + +### Using polymorphic variants to bind to event listeners + +Polymorphic variants can also be used to wrap event listeners, or any other kind +of callback, for example: + +```ocaml +type readline + +external on : + readline -> + ([ `close of unit -> unit | `line of string -> unit ][@mel.string]) -> + readline = "on" + [@@mel.send] + +let register rl = + rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) +``` +```reasonml +type readline; + +[@mel.send] +external on: + ( + readline, + [@mel.string] [ | `close(unit => unit) | `line(string => unit)] + ) => + readline = + "on"; + +let register = rl => + rl->(on(`close(event => ())))->(on(`line(line => Js.log(line)))); +``` + +This generates: + +```js +function register(rl) { + return rl + .on("close", function($$event) {}) + .on("line", function(line) { + console.log(line); + }); +} +``` + +### Constant values as arguments + +Sometimes we want to call a JavaScript function and make sure one of the +arguments is always constant. For this, the `[@mel.as]` attribute can be +combined with the wildcard pattern `_`: + +```ocaml +external process_on_exit : (_[@mel.as "exit"]) -> (int -> unit) -> unit + = "process.on" + +let () = + process_on_exit (fun exit_code -> + Js.log ("error code: " ^ string_of_int exit_code)) +``` +```reasonml +external process_on_exit: ([@mel.as "exit"] _, int => unit) => unit = + "process.on"; + +let () = + process_on_exit(exit_code => + Js.log("error code: " ++ string_of_int(exit_code)) + ); +``` + +This generates: + +```js +process.on("exit", function (exitCode) { + console.log("error code: " + exitCode.toString()); +}); +``` + +The `mel.as "exit"` and the wildcard `_` pattern together will tell Melange to +compile the first argument of the JavaScript function to the string `"exit"`. + +You can also use any JSON literal by passing a quoted string to `mel.as`: +`mel.as {json|true|json}` or `mel.as {json|{"name": "John"}|json}`. + +### Binding to callbacks + +In OCaml, all functions have arity 1. This means that if you define a function +like this: + +```ocaml +let add x y = x + y +``` +```reasonml +let add = (x, y) => x + y; +``` + +Its type will be `int -> int -> int`. This means that one can partially apply +`add` by calling `add 1`, which will return another function expecting the +second argument of the addition. This kind of functions are called "curried" +functions, more information about currying in OCaml can be found in [this +chapter](https://cs3110.github.io/textbook/chapters/hop/currying.html) of the +"OCaml Programming: Correct + Efficient + Beautiful" book. + +This is incompatible with how function calling conventions work in JavaScript, +where all function calls always apply all the arguments. To continue the +example, let’s say we have an `add` function implemented in JavaScript, similar +to the one above: + +```javascript +var add = function (a, b) { + return a + b; +}; +``` + +If we call `add(1)`, the function will be totally applied, with `b` having +`undefined` value. And as JavaScript will try to add `1` with `undefined`, we +will get `NaN` as a result. + +To illustrate this difference and how it affects Melange bindings, let’s say we +want to write bindings for a JavaScript function like this: + +```javascript +function map (a, b, f){ + var i = Math.min(a.length, b.length); + var c = new Array(i); + for(var j = 0; j < i; ++j){ + c[j] = f(a[i],b[i]) + } + return c ; +} +``` + +A naive external function declaration could be as below: + +```ocaml +external map : 'a array -> 'b array -> ('a -> 'b -> 'c) -> 'c array = "map" +``` +```reasonml +external map: (array('a), array('b), ('a, 'b) => 'c) => array('c) = "map"; +``` + +Unfortunately, this is not completely correct. The issue is in the callback +function, with type `'a -> 'b -> 'c`. This means that `map` will expect a +function like `add` described above. But as we said, in OCaml, having two +arguments means just to have two functions that take one argument. + +Let’s rewrite `add` to make the problem a bit more clear: + +```ocaml +let add x = let partial y = x + y in partial +``` +```reasonml +let add = x => { + let partial = y => x + y; + partial; +}; +``` + +This will be compiled to: + +```javascript +function add(x) { + return (function (y) { + return x + y | 0; + }); +} +``` + +Now if we ever used our external function `map` with our `add` function by +calling `map arr1 arr2 add` it would not work as expected. JavaScript function +application does not work the same as in OCaml, so the function call in the +`map` implementation, `f(a[i],b[i])`, would be applied over the outer JavaScript +function `add`, which only takes one argument `x`, and `b[i]` would be just +discarded. The value returned from the operation would not be the addition of +the two numbers, but rather the inner anonymous callback. + +To solve this mismatch between OCaml and JavaScript functions and their +application, Melange provides a special attribute `@u` that can be used to +annotate external functions that need to be "uncurried". + +In Reason syntax, this attribute does not need to be +written explicitly, as it is deeply integrated with the Reason parser. To +specify some function type as "uncurried", one just needs to add the dot +character `.` to the function type. For example, `(. 'a, 'b) => 'c` instead of +`('a, 'b) => 'c`. + +In the example above: + +```ocaml +external map : 'a array -> 'b array -> (('a -> 'b -> 'c)[@u]) -> 'c array + = "map" +``` +```reasonml +external map: (array('a), array('b), (. 'a, 'b) => 'c) => array('c) = "map"; +``` + +Here `('a -> 'b -> 'c [@u])``(. 'a, 'b) => 'c`will be interpreted as having +arity 2. In general, `'a0 -> 'a1 ...​ 'aN -> 'b0 [@u]` +is the same as `'a0 -> 'a1 ...​ 'aN -> 'b0``. +'a0, 'a1, ...​ 'aN => 'b0` is the same as `'a0, 'a1, ...​ 'aN => 'b0` +except the former’s arity is guaranteed to be N while the latter is unknown. + +If we try now to call `map` using `add`: + +```ocaml +let add x y = x + y +let _ = map [||] [||] add +``` +```reasonml +let add = (x, y) => x + y; +let _ = map([||], [||], add); +``` +We will get an error: + +```text +let _ = map [||] [||] add + ^^^ +This expression has type int -> int -> int +but an expression was expected of type ('a -> 'b -> 'c) Js.Fn.arity2 +``` + +To solve this, we add `@u``.` in the function definition as well: + +```ocaml +let add = fun [@u] x y -> x + y +``` +```reasonml +let add = (. x, y) => x + y; +``` + +Annotating function definitions can be quite cumbersome when writing a lot of +externals. + +To work around the verbosity, Melange offers another attribute called +`mel.uncurry`. + +Let’s see how we could use it in the previous example. We just need to replace +`u` with `mel.uncurry`: + +```ocaml +external map : + 'a array -> 'b array -> (('a -> 'b -> 'c)[@mel.uncurry]) -> 'c array = "map" +``` +```reasonml +external map: + (array('a), array('b), [@mel.uncurry] (('a, 'b) => 'c)) => array('c) = + "map"; +``` + +Now if we try to call `map` with a regular `add` function: + +```ocaml +let add x y = x + y +let _ = map [||] [||] add +``` +```reasonml +let add = (x, y) => x + y; +let _ = map([||], [||], add); +``` + +Everything works fine now, without having to attach any attributes to `add`. + +The main difference between `u` and `mel.uncurry` is that the latter only works +with externals. `mel.uncurry` is the recommended option to use for bindings, +while `u` remains useful for those use cases where performance is crucial and we +want the JavaScript functions generated from OCaml ones to not be applied +partially. + +### Modeling `this`\-based Callbacks + +Many JavaScript libraries have callbacks which rely on the [`this` +keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this), +for example: + +```js +x.onload = function(v) { + console.log(this.response + v) +} +``` + +Inside the `x.onload` callback, `this` would be pointing to `x`. It would not be +correct to declare `x.onload` of type `unit -> unit`. Instead, Melange +introduces a special attribute, `mel.this`, which allows to type `x` as this: + +```ocaml +type x +external x : x = "x" +external set_onload : x -> ((x -> int -> unit)[@mel.this]) -> unit = "onload" + [@@mel.set] +external resp : x -> int = "response" [@@mel.get] +let _ = + set_onload x + begin + fun [@mel.this] o v -> Js.log (resp o + v) + end +``` +```reasonml +type x; +external x: x = "x"; +[@mel.set] +external set_onload: (x, [@mel.this] ((x, int) => unit)) => unit = "onload"; +[@mel.get] external resp: x => int = "response"; +let _ = set_onload(x, [@mel.this] (o, v) => Js.log(resp(o) + v)); +``` + +Which generates: + +```javascript +x.onload = function (v) { + var o = this; + console.log((o.response + v) | 0); +}; +``` + +Note that the first argument will be reserved for `this`. + +### Wrapping returned nullable values + +JavaScript models `null` and `undefined` differently, whereas it can be useful +to treat both as `'a option``option('a)` in Melange. + +Melange understands the `mel.return` attribute in externals to model how +nullable return types should be wrapped at the bindings boundary. An `external` +value with `mel.return` converts the return value to an `option` type, avoiding +the need for extra wrapping / unwrapping with functions such as +`Js.Nullable.toOption`. + +```ocaml +type element +type document +external get_by_id : document -> string -> element option = "getElementById" + [@@mel.send] [@@mel.return nullable] + +let test document = + let elem = get_by_id document "header" in + match elem with + | None -> 1 + | Some _element -> 2 +``` +```reasonml +type element; +type document; +[@mel.send] [@mel.return nullable] +external get_by_id: (document, string) => option(element) = "getElementById"; + +let test = document => { + let elem = get_by_id(document, "header"); + switch (elem) { + | None => 1 + | Some(_element) => 2 + }; +}; +``` + +Which generates: + +```js +function test($$document) { + var elem = $$document.getElementById("header"); + if (elem == null) { + return 1; + } else { + return 2; + } +} +``` + +The `mel.return` attribute takes an attribute payload, as seen with `[@@mel.return nullable]``[@mel.return nullable]` above. Currently 4 +directives are supported: `null_to_opt`, `undefined_to_opt`, `nullable` and +`identity`. + +`nullable` is encouraged, as it will convert from `null` and `undefined` to +`option` type. + + + +`identity` will make sure that compiler will do nothing about the returned +value. It is rarely used, but introduced here for debugging purposes. diff --git a/scripts/advanced-js-interop.t b/scripts/advanced-js-interop.t new file mode 100644 index 000000000..df829c562 --- /dev/null +++ b/scripts/advanced-js-interop.t @@ -0,0 +1,279 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > let default = 10 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let print name = "Hello" ^ name + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = private { + > name : string; + > age : int; + > } + > [@@deriving getSet] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > mutable age : int; + > } + > [@@deriving getSet] + > + > let alice = person ~name:"Alice" ~age:20 + > + > let () = ageSet alice 21 + > EOF + + $ dune build @melange + File "input.ml", line 7, characters 12-18: + 7 | let alice = person ~name:"Alice" ~age:20 + ^^^^^^ + Error: Unbound value person + [1] + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > age : int; + > } + > [@@deriving jsProperties, getSet { light }] + > + > let alice = person ~name:"Alice" ~age:20 + > let aliceName = name alice + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > type person = { + > name : string; + > age : int option; [@mel.optional] + > } + > [@@deriving jsProperties, getSet] + > let alice = person ~name:"Alice" ~age:20 () + > let bob = person ~name:"Bob" () + > + > let twenty = ageGet alice + > + > let bob = nameGet bob + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > age : int option; [@mel.optional] + > } + > [@@deriving jsProperties, getSet] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > age : int option; [@mel.optional] + > } + > [@@deriving getSet] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > type person = { + > name : string; + > age : int option; [@mel.optional] + > } + > [@@deriving jsDeriving] + > + > let alice = person ~name:"Alice" ~age:20 () + > let bob = person ~name:"Bob" () + > EOF + + $ dune build @melange + File "input.ml", line 4, characters 22-34: + 4 | age : int option; [@mel.optional] + ^^^^^^^^^^^^ + Alert unused: Unused attribute [@mel.optional] + This means such annotation is not annotated properly. + For example, some annotations are only meaningful in externals + + File "input.ml", line 6, characters 12-22: + 6 | [@@deriving jsDeriving] + ^^^^^^^^^^ + Error: Ppxlib.Deriving: 'jsDeriving' is not a supported type deriving + generator + [1] + + $ cat > input.ml <<\EOF + > type person + > + > val person : name:string -> ?age:int -> unit -> person + > EOF + + $ dune build @melange + File "input.ml", line 3, characters 0-54: + 3 | val person : name:string -> ?age:int -> unit -> person + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Error: Value declarations are only allowed in signatures + [1] + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > age : int option; [@mel.optional] + > } + > [@@deriving jsProperties] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > age : int option; + > } + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type pet = { name : string } [@@deriving accessors] + > let name (param : pet) = param.name + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type pet = { name : string } [@@deriving accessors] + > + > let pets = [| { name = "Brutus" }; { name = "Mochi" } |] + > + > let () = pets |. Belt.Array.map name |. Js.Array.join ~sep:"&" |. Js.log + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > val actionToJs : action -> string + > + > val actionFromJs : string -> action option + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 17-23: + 1 | val actionToJs : action -> string + ^^^^^^ + Error: Unbound type constructor action + Hint: Did you mean option? + [1] + + $ cat > input.ml <<\EOF + > type action = + > [ `Click + > | `Submit [@mel.as "submit"] + > | `Cancel + > ] + > [@@deriving jsConverter] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > val actionToJs : action -> abs_action + > + > val actionFromJs : abs_action -> action + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 17-23: + 1 | val actionToJs : action -> abs_action + ^^^^^^ + Error: Unbound type constructor action + Hint: Did you mean option? + [1] + + $ cat > input.ml <<\EOF + > type action = + > | Click + > | Submit [@mel.as 3] + > | Cancel + > [@@deriving jsConverter { newType }] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > val actionToJs : action -> int + > + > val actionFromJs : int -> action option + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 17-23: + 1 | val actionToJs : action -> int + ^^^^^^ + Error: Unbound type constructor action + Hint: Did you mean option? + [1] + + $ cat > input.ml <<\EOF + > type action = + > | Click + > | Submit [@mel.as 3] + > | Cancel + > [@@deriving jsConverter] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type action = + > | Click + > | Submit of string + > | Cancel + > + > let click = (Click : action) + > let submit param = (Submit param : action) + > let cancel = (Cancel : action) + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type action = + > | Click + > | Submit of string + > | Cancel + > [@@deriving accessors] + > EOF + + $ dune build @melange + diff --git a/scripts/attributes-and-extension-nodes.t b/scripts/attributes-and-extension-nodes.t new file mode 100644 index 000000000..b0b2596f2 --- /dev/null +++ b/scripts/attributes-and-extension-nodes.t @@ -0,0 +1,77 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > external node_env : string = "NODE_ENV" [@@mel.scope "process", "env"] + > + > let development = "development" + > let () = if node_env <> development then Js.log "Only in Production" + > + > let development_inline = "development" [@@mel.inline] + > let () = if node_env <> development_inline then Js.log "Only in Production" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let () = match [%mel.external __filename] with + > | Some f -> Js.log f + > | None -> Js.log "non-node environment" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let () = match [%mel.external __DEV__] with + > | Some _ -> Js.log "dev mode" + > | None -> Js.log "production mode" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let f x y = + > [%mel.debugger]; + > x + y + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > [%%mel.raw "var a = 1"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let f : unit -> int = [%mel.raw "function() {return 1}"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add = [%mel.raw {| + > function(a, b) { + > console.log("hello from raw JavaScript!"); + > return a + b; + > } + > |}] + > + > let () = Js.log (add 1 2) + > EOF + + $ dune build @melange + diff --git a/scripts/bindings-cookbook.t b/scripts/bindings-cookbook.t new file mode 100644 index 000000000..0697a6ba2 --- /dev/null +++ b/scripts/bindings-cookbook.t @@ -0,0 +1,296 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let arr = [|2|] + > external sort : 'a array -> (('a -> 'a -> int)[@mel.uncurry]) -> 'a array + > = "sort" + > [@@mel.send] + > + > let _ = sort arr compare + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > module Foo = struct + > type t + > external make : unit -> t = "Foo" [@@mel.new] + > end + > + > let foo = Foo.make () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > module Foo = struct + > type t + > external make : unit -> t = "Foo" [@@mel.new] + > external bar : t -> unit = "bar" [@@mel.get] + > end + > + > let foo = Foo.make () + > let () = Foo.bar foo + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { id : int; name : string } + > + > let person = { id = 1; name = "Alice" } + > let { id; name } = person + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > type person = { id : int; name : string } + > let person = { id = 1; name = "Alice" } + > + > external set_id : person -> int -> unit = "id" [@@mel.set] + > + > let () = set_id person 0 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > type person = { id : int; name : string } + > let person = { id = 1; name = "Alice" } + > + > let name = person.name + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > + > let person = [%mel.obj { name = "john"; age = 99 }] + > + > let name = person##name + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { id : int; name : string } + > let person = { id = 1; name = "Alice" } + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let person = [%mel.obj { id = 1; name = "Alice" }] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external forEach : + > start:int -> stop:int -> ((int -> unit)[@mel.uncurry]) -> unit = "forEach" + > + > let () = forEach ~start:1 ~stop:10 Js.log + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > 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 ()) () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external range : start:int -> stop:int -> ?step:int -> unit -> int array + > = "range" + > + > let nums = range ~start:1 ~stop:10 () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external fooString : string -> unit = "foo" + > external fooBool : bool -> unit = "foo" + > + > let () = fooString "" + > let () = fooBool true + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external range : start:int -> stop:int -> step:int -> int array = "range" + > let nums = range ~start:1 ~stop:10 ~step:2 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external join : string array -> string = "join" [@@mel.module "path"] [@@mel.variadic] + > let dir = join [| "a"; "b" |] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > module Foo = struct + > module Bar = struct + > external baz : unit -> unit = "baz" [@@mel.module "foo"] [@@mel.scope "bar"] + > end + > end + > + > let () = Foo.Bar.baz () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external foo : int -> unit = "default" [@@mel.module "foo"] + > let () = foo 1 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external foo : int -> unit = "foo" [@@mel.module] + > let () = foo 1 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external join : string -> string -> string = "join" [@@mel.module "path"] + > let dir = join "a" "b" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external log : 'a -> unit = "log" [@@mel.scope "console"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external pi : float = "PI" [@@mel.scope "Math"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let _ = match [%mel.external window] with + > | Some _ -> "window exists" + > | None -> "window does not exist" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external window : Dom.window = "window" + > EOF + + $ dune build @melange + diff --git a/scripts/communicate-with-javascript.t b/scripts/communicate-with-javascript.t index 89db13df1..20635b913 100644 --- a/scripts/communicate-with-javascript.t +++ b/scripts/communicate-with-javascript.t @@ -15,1297 +15,7 @@ file. To update the tests, run `dune build @extract-code-blocks`. > EOF $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let arr = [|2|] - > external sort : 'a array -> (('a -> 'a -> int)[@mel.uncurry]) -> 'a array - > = "sort" - > [@@mel.send] - > - > let _ = sort arr compare - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > module Foo = struct - > type t - > external make : unit -> t = "Foo" [@@mel.new] - > end - > - > let foo = Foo.make () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > module Foo = struct - > type t - > external make : unit -> t = "Foo" [@@mel.new] - > external bar : t -> unit = "bar" [@@mel.get] - > end - > - > let foo = Foo.make () - > let () = Foo.bar foo - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { id : int; name : string } - > - > let person = { id = 1; name = "Alice" } - > let { id; name } = person - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > type person = { id : int; name : string } - > let person = { id = 1; name = "Alice" } - > - > external set_id : person -> int -> unit = "id" [@@mel.set] - > - > let () = set_id person 0 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > type person = { id : int; name : string } - > let person = { id = 1; name = "Alice" } - > - > let name = person.name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > let person = [%mel.obj { name = "john"; age = 99 }] - > - > let name = person##name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { id : int; name : string } - > let person = { id = 1; name = "Alice" } - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let person = [%mel.obj { id = 1; name = "Alice" }] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external forEach : - > start:int -> stop:int -> ((int -> unit)[@mel.uncurry]) -> unit = "forEach" - > - > let () = forEach ~start:1 ~stop:10 Js.log - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > 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 ()) () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external range : start:int -> stop:int -> ?step:int -> unit -> int array - > = "range" - > - > let nums = range ~start:1 ~stop:10 () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external fooString : string -> unit = "foo" - > external fooBool : bool -> unit = "foo" - > - > let () = fooString "" - > let () = fooBool true - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external range : start:int -> stop:int -> step:int -> int array = "range" - > let nums = range ~start:1 ~stop:10 ~step:2 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external join : string array -> string = "join" [@@mel.module "path"] [@@mel.variadic] - > let dir = join [| "a"; "b" |] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > module Foo = struct - > module Bar = struct - > external baz : unit -> unit = "baz" [@@mel.module "foo"] [@@mel.scope "bar"] - > end - > end - > - > let () = Foo.Bar.baz () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external foo : int -> unit = "default" [@@mel.module "foo"] - > let () = foo 1 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external foo : int -> unit = "foo" [@@mel.module] - > let () = foo 1 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external join : string -> string -> string = "join" [@@mel.module "path"] - > let dir = join "a" "b" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external log : 'a -> unit = "log" [@@mel.scope "console"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external pi : float = "PI" [@@mel.scope "Math"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let _ = match [%mel.external window] with - > | Some _ -> "window exists" - > | None -> "window does not exist" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external window : Dom.window = "window" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let default = 10 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let print name = "Hello" ^ name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = private { - > name : string; - > age : int; - > } - > [@@deriving getSet] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > mutable age : int; - > } - > [@@deriving getSet] - > - > let alice = person ~name:"Alice" ~age:20 - > - > let () = ageSet alice 21 - > EOF - - $ dune build @melange - File "input.ml", line 7, characters 12-18: - 7 | let alice = person ~name:"Alice" ~age:20 - ^^^^^^ - Error: Unbound value person - [1] - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > age : int; - > } - > [@@deriving jsProperties, getSet { light }] - > - > let alice = person ~name:"Alice" ~age:20 - > let aliceName = name alice - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > type person = { - > name : string; - > age : int option; [@mel.optional] - > } - > [@@deriving jsProperties, getSet] - > let alice = person ~name:"Alice" ~age:20 () - > let bob = person ~name:"Bob" () - > - > let twenty = ageGet alice - > - > let bob = nameGet bob - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > age : int option; [@mel.optional] - > } - > [@@deriving jsProperties, getSet] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > age : int option; [@mel.optional] - > } - > [@@deriving getSet] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > - > type person = { - > name : string; - > age : int option; [@mel.optional] - > } - > [@@deriving jsDeriving] - > - > let alice = person ~name:"Alice" ~age:20 () - > let bob = person ~name:"Bob" () - > EOF - - $ dune build @melange - File "input.ml", line 4, characters 22-34: - 4 | age : int option; [@mel.optional] - ^^^^^^^^^^^^ - Alert unused: Unused attribute [@mel.optional] - This means such annotation is not annotated properly. - For example, some annotations are only meaningful in externals - - File "input.ml", line 6, characters 12-22: - 6 | [@@deriving jsDeriving] - ^^^^^^^^^^ - Error: Ppxlib.Deriving: 'jsDeriving' is not a supported type deriving - generator - [1] - - $ cat > input.ml <<\EOF - > type person - > - > val person : name:string -> ?age:int -> unit -> person - > EOF - - $ dune build @melange - File "input.ml", line 3, characters 0-54: - 3 | val person : name:string -> ?age:int -> unit -> person - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Error: Value declarations are only allowed in signatures - [1] - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > age : int option; [@mel.optional] - > } - > [@@deriving jsProperties] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > age : int option; - > } - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type pet = { name : string } [@@deriving accessors] - > let name (param : pet) = param.name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type pet = { name : string } [@@deriving accessors] - > - > let pets = [| { name = "Brutus" }; { name = "Mochi" } |] - > - > let () = pets |. Belt.Array.map name |. Js.Array.join ~sep:"&" |. Js.log - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > val actionToJs : action -> string - > - > val actionFromJs : string -> action option - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 17-23: - 1 | val actionToJs : action -> string - ^^^^^^ - Error: Unbound type constructor action - Hint: Did you mean option? - [1] - - $ cat > input.ml <<\EOF - > type action = - > [ `Click - > | `Submit [@mel.as "submit"] - > | `Cancel - > ] - > [@@deriving jsConverter] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > val actionToJs : action -> abs_action - > - > val actionFromJs : abs_action -> action - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 17-23: - 1 | val actionToJs : action -> abs_action - ^^^^^^ - Error: Unbound type constructor action - Hint: Did you mean option? - [1] - - $ cat > input.ml <<\EOF - > type action = - > | Click - > | Submit [@mel.as 3] - > | Cancel - > [@@deriving jsConverter { newType }] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > val actionToJs : action -> int - > - > val actionFromJs : int -> action option - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 17-23: - 1 | val actionToJs : action -> int - ^^^^^^ - Error: Unbound type constructor action - Hint: Did you mean option? - [1] - - $ cat > input.ml <<\EOF - > type action = - > | Click - > | Submit [@mel.as 3] - > | Cancel - > [@@deriving jsConverter] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type action = - > | Click - > | Submit of string - > | Cancel - > - > let click = (Click : action) - > let submit param = (Submit param : action) - > let cancel = (Cancel : action) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type action = - > | Click - > | Submit of string - > | Cancel - > [@@deriving accessors] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type element - > type document - > external get_by_id : document -> string -> element option = "getElementById" - > [@@mel.send] [@@mel.return nullable] - > - > let test document = - > let elem = get_by_id document "header" in - > match elem with - > | None -> 1 - > | Some _element -> 2 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type x - > external x : x = "x" - > external set_onload : x -> ((x -> int -> unit)[@mel.this]) -> unit = "onload" - > [@@mel.set] - > external resp : x -> int = "response" [@@mel.get] - > let _ = - > set_onload x - > begin - > fun [@mel.this] o v -> Js.log (resp o + v) - > end - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add x y = x + y - > let _ = map [||] [||] add - > EOF - - $ dune build @melange - File "input.ml", line 2, characters 8-11: - 2 | let _ = map [||] [||] add - ^^^ - Error: Unbound value map - Hint: Did you mean max? - [1] - - $ cat > input.ml <<\EOF - > external map : - > 'a array -> 'b array -> (('a -> 'b -> 'c)[@mel.uncurry]) -> 'c array = "map" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add = fun [@u] x y -> x + y - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add x y = x + y - > let _ = map [||] [||] add - > EOF - - $ dune build @melange - File "input.ml", line 2, characters 8-11: - 2 | let _ = map [||] [||] add - ^^^ - Error: Unbound value map - Hint: Did you mean max? - [1] - - $ cat > input.ml <<\EOF - > external map : 'a array -> 'b array -> (('a -> 'b -> 'c)[@u]) -> 'c array - > = "map" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add x = let partial y = x + y in partial - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external map : 'a array -> 'b array -> ('a -> 'b -> 'c) -> 'c array = "map" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add x y = x + y - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external process_on_exit : (_[@mel.as "exit"]) -> (int -> unit) -> unit - > = "process.on" - > - > let () = - > process_on_exit (fun exit_code -> - > Js.log ("error code: " ^ string_of_int exit_code)) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type readline - > - > external on : - > readline -> - > ([ `close of unit -> unit | `line of string -> unit ][@mel.string]) -> - > readline = "on" - > [@@mel.send] - > - > let register rl = - > rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) - > EOF - - $ dune build @melange - File "input.ml", line 10, characters 24-29: - 10 | rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) - ^^^^^ - Error (warning 27 [unused-var-strict]): unused variable event. - [1] - - $ cat > input.ml <<\EOF - > external test_int_type : - > ([ `on_closed | `on_open [@mel.as 20] | `in_bin ][@mel.int]) -> int - > = "testIntType" - > - > let value = test_int_type `on_open - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type document - > type style - > - > external document : document = "document" - > external get_by_id : document -> string -> Dom.element = "getElementById" - > [@@mel.send] - > external style : Dom.element -> style = "style" [@@mel.get] - > external transition_timing_function : - > style -> - > ([ `ease - > | `easeIn [@mel.as "ease-in"] - > | `easeOut [@mel.as "ease-out"] - > | `easeInOut [@mel.as "ease-in-out"] - > | `linear ] - > [@mel.string]) -> - > unit = "transitionTimingFunction" - > [@@mel.set] - > - > let element_style = style (get_by_id document "my-id") - > let () = transition_timing_function element_style `easeIn - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external read_file_sync : - > name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" - > [@@mel.module "fs"] - > - > let _ = read_file_sync ~name:"xx.txt" `ascii - > EOF - - $ dune build @melange - File "input.ml", line 2, characters 18-36: - 2 | name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" - ^^^^^^^^^^^^^^^^^^ - Alert redundant: [@mel.string] is redundant here, you can safely remove it - - $ cat > input.ml <<\EOF - > external padLeft: - > string - > -> ([ `Str of string - > | `Int of int - > ] [@mel.unwrap]) - > -> string - > = "padLeft" - > - > let _ = padLeft "Hello World" (`Int 4) - > let _ = padLeft "Hello World" (`Str "Message from Melange: ") - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external drawCat : unit -> unit = "draw" [@@mel.module "MyGame"] - > external drawDog : giveName:string -> unit = "draw" [@@mel.module "MyGame"] - > external draw : string -> useRandomAnimal:bool -> unit = "draw" - > [@@mel.module "MyGame"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type hide = Hide : 'a -> hide [@@unboxed] - > - > external join : hide array -> string = "join" [@@mel.module "path"] [@@mel.variadic] - > - > let v = join [| Hide "a"; Hide 2 |] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external join : string array -> string = "join" - > [@@mel.module "path"] [@@mel.variadic] - > let v = join [| "a"; "b" |] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for the `document` global *) - > type document - > - > external document : document = "document" - > external get_by_id : string -> Dom.element = "getElementById" - > [@@mel.send.pipe: document] - > external get_by_classname : string -> Dom.element = "getElementsByClassName" - > [@@mel.send.pipe: Dom.element] - > - > let el = document |> get_by_id "my-id" |> get_by_classname "my-class" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for the `document` global *) - > type document - > - > external document : document = "document" - > external get_by_id : document -> string -> Dom.element = "getElementById" - > [@@mel.send] - > external get_by_classname : Dom.element -> string -> Dom.element - > = "getElementsByClassName" - > [@@mel.send] - > - > let el = document |. get_by_id "my-id" |. get_by_classname "my-class" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for the `document` global *) - > type document - > - > external document : document = "document" - > external get_by_id : string -> Dom.element = "getElementById" - > [@@mel.send.pipe: document] - > - > let el = get_by_id "my-id" document - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for the `document` global *) - > type document - > - > external document : document = "document" - > external get_by_id : document -> string -> Dom.element = "getElementById" - > [@@mel.send] - > - > let el = get_by_id document "my-id" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" - > [@@mel.module "MyGame"] - > let () = draw ~x:10 ~y:20 () - > let () = draw ~y:20 ~x:10 () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" - > [@@mel.module "MyGame"] - > - > let () = draw ~x:10 ~y:20 ~border:true () - > let () = draw ~x:10 ~y:20 () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t - > - > external create : unit -> t = "GUI" - > [@@mel.new] [@@mel.scope "default"] [@@mel.module "dat.gui"] - > - > let gui = create () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external imul : int -> int -> int = "imul" [@@mel.scope "Math"] - > - > let res = imul 1 2 - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t - > - > external back : t = "back" - > [@@mel.module "expo-camera"] [@@mel.scope "Camera", "Constants", "Type"] - > - > let camera_type_back = back - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type param - > external executeCommands : string -> param array -> unit = "" - > [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] - > - > let f a b c = executeCommands "hi" [| a; b; c |] - > EOF - - $ dune build @melange - File "input.ml", lines 2-3, characters 0-67: - 2 | external executeCommands : string -> param array -> unit = "" - 3 | [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] - Alert fragile: executeCommands : the external name is inferred from val name is unsafe from refactoring when changing value name - - $ cat > input.ml <<\EOF - > external dirname : string -> string = "dirname" [@@mel.module "path"] - > let root = dirname "/User/github" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for `document` *) - > type document - > - > external document : document = "document" - > let document = document - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for `timeoutId` *) - > type timeoutId - > external setTimeout : (unit -> unit) -> int -> timeoutId = "setTimeout" - > external clearTimeout : timeoutId -> unit = "clearTimeout" - > - > let id = setTimeout (fun () -> Js.log "hello") 100 - > let () = clearTimeout id - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t - > external book : unit -> t = "Book" [@@mel.new] [@@mel.module] - > let myBook = book () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t - > external create_date : unit -> t = "Date" [@@mel.new] - > let date = create_date () - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t - > external create : int -> t = "Int32Array" [@@mel.new] - > external get : t -> int -> int = "" [@@mel.get_index] - > external set : t -> int -> int -> unit = "" [@@mel.set_index] - > - > let () = - > let i32arr = (create 3) in - > set i32arr 0 42; - > Js.log (get i32arr 0) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > (* Abstract type for the `document` value *) - > type document - > - > external document : document = "document" - > - > external set_title : document -> string -> unit = "title" [@@mel.set] - > external get_title : document -> string = "title" [@@mel.get] - > - > let current = get_title document - > let () = set_title document "melange" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 16-21: - 1 | let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () - ^^^^^ - Error: Unbound value route - [1] - - $ cat > input.ml <<\EOF - > external route : - > _type:string -> - > path:string -> - > action:(string list -> unit) -> - > ?options:< .. > -> - > unit -> - > _ = "" - > [@@mel.obj] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let name_extended obj = obj##name ^ " wayne" - > - > let one = name_extended [%mel.obj { name = "john"; age = 99 }] - > let two = name_extended [%mel.obj { name = "jane"; address = "1 infinite loop" }] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let john = [%mel.obj { name = "john"; age = 99 }] - > let t = john##name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t = { - > foo : int; [@mel.as "0"] - > bar : string; [@mel.as "1"] - > } - > - > let value = { foo = 7; bar = "baz" } - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type action = { - > type_ : string [@mel.as "type"] - > } - > - > let action = { type_ = "ADD_USER" } - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type person = { - > name : string; - > friends : string array; - > age : int; - > } - > - > external john : person = "john" [@@mel.module "MySchool"] - > let john_name = john.name - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external node_env : string = "NODE_ENV" [@@mel.scope "process", "env"] - > - > let development = "development" - > let () = if node_env <> development then Js.log "Only in Production" - > - > let development_inline = "development" [@@mel.inline] - > let () = if node_env <> development_inline then Js.log "Only in Production" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let () = match [%mel.external __filename] with - > | Some f -> Js.log f - > | None -> Js.log "non-node environment" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let () = match [%mel.external __DEV__] with - > | Some _ -> Js.log "dev mode" - > | None -> Js.log "production mode" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let f x y = - > [%mel.debugger]; - > x + y - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > [%%mel.raw "var a = 1"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let f : unit -> int = [%mel.raw "function() {return 1}"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let add = [%mel.raw {| - > function(a, b) { - > console.log("hello from raw JavaScript!"); - > return a + b; - > } - > |}] - > - > let () = Js.log (add 1 2) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let r = [%mel.re "/b/g"] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let () = React.useEffect2 (fun () -> None) (foo, bar) - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 9-25: - 1 | let () = React.useEffect2 (fun () -> None) (foo, bar) - ^^^^^^^^^^^^^^^^ - Error: Unbound module React - [1] - - $ cat > input.ml <<\EOF - > let world = {j|世界|j} - > let helloWorld = {j|你好,$world|j} - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let () = Js.log {js|你好, - > 世界|js} - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let () = Js.log "你好" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let u = `Foo (* "Foo" *) - > let v = `Foo(2) (* { NAME: "Foo", VAL: "2" } *) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type t = A of string | B of int - > (* A("foo") -> { TAG: 0, _0: "Foo" } *) - > (* B(2) -> { TAG: 1, _0: 2 } *) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type tree = Leaf | Node of int * tree * tree - > (* Leaf -> 0 *) - > (* Node(7, Leaf, Leaf) -> { _0: 7, _1: 0, _2: 0 } *) - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let sum_sq = - > [ 1; 2; 3 ] - > |. Belt.List.map String.cat - > |. sum - > EOF - - $ dune build @melange - File "input.ml", line 4, characters 5-8: - 4 | |. sum - ^^^ - Error: Unbound value sum - [1] - - $ cat > input.ml <<\EOF - > let sum_sq = - > [ 1; 2; 3 ] - > |. Belt.List.map square - > |. sum - > EOF - - $ dune build @melange - File "input.ml", line 4, characters 5-8: - 4 | |. sum - ^^^ - Error: Unbound value sum - [1] - - $ cat > input.ml <<\EOF - > let sum_sq = - > [ 1; 2; 3 ] - > |> List.map String.cat - > |> sum - > EOF - - $ dune build @melange - File "input.ml", line 4, characters 5-8: - 4 | |> sum - ^^^ - Error: Unbound value sum - [1] - - $ cat > input.ml <<\EOF - > let sum = List.fold_left ( + ) 0 - > - > let sum_sq = - > [ 1; 2; 3 ] - > |> List.map square (* [1; 4; 9] *) - > |> sum (* 1 + 4 + 9 *) - > EOF - - $ dune build @melange - File "input.ml", line 5, characters 14-20: - 5 | |> List.map square (* [1; 4; 9] *) - ^^^^^^ - Error: Unbound value square - [1] - - $ cat > input.ml <<\EOF - > let ten = 3 |> square |> succ - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 15-21: - 1 | let ten = 3 |> square |> succ - ^^^^^^ - Error: Unbound value square - [1] - - $ cat > input.ml <<\EOF - > let ten = succ (square 3) - > EOF - - $ dune build @melange - File "input.ml", line 1, characters 16-22: - 1 | let ten = succ (square 3) - ^^^^^^ - Error: Unbound value square - [1] - - $ cat > input.ml <<\EOF - > let square x = x * x - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > let ( |> ) f g = g f - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type document - > - > external document : document = "document" - > external set_title : document -> string -> unit = "title" [@@mel.set] - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type document - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type foo = string - > type bar = int - > external danger_zone : foo -> bar = "%identity" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > external my_c_function : int -> string = "someCFunctionName" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type document - > external setTitleDom : document -> string -> unit = "title" [@@mel.set] - > - > type t = { - > age : int; [@mel.as "a"] - > name : string; [@mel.as "n"] - > } - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > type name = - > | Name of string [@@unboxed] - > let student_name = Name "alice" - > EOF - - $ dune build @melange - - $ cat > input.ml <<\EOF - > [%%mel.raw "var a = 1; var b = 2"] - > let add = [%mel.raw "a + b"] + > external confirm : string -> bool = "window.confirm" > EOF $ dune build @melange diff --git a/scripts/data-types-and-runtime-rep.t b/scripts/data-types-and-runtime-rep.t new file mode 100644 index 000000000..6ecdd6908 --- /dev/null +++ b/scripts/data-types-and-runtime-rep.t @@ -0,0 +1,76 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > let r = [%mel.re "/b/g"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let () = React.useEffect2 (fun () -> None) (foo, bar) + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 9-25: + 1 | let () = React.useEffect2 (fun () -> None) (foo, bar) + ^^^^^^^^^^^^^^^^ + Error: Unbound module React + [1] + + $ cat > input.ml <<\EOF + > let world = {j|世界|j} + > let helloWorld = {j|你好,$world|j} + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let () = Js.log {js|你好, + > 世界|js} + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let () = Js.log "你好" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let u = `Foo (* "Foo" *) + > let v = `Foo(2) (* { NAME: "Foo", VAL: "2" } *) + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t = A of string | B of int + > (* A("foo") -> { TAG: 0, _0: "Foo" } *) + > (* B(2) -> { TAG: 1, _0: 2 } *) + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type tree = Leaf | Node of int * tree * tree + > (* Leaf -> 0 *) + > (* Node(7, Leaf, Leaf) -> { _0: 7, _1: 0, _2: 0 } *) + > EOF + + $ dune build @melange + diff --git a/scripts/dune b/scripts/dune index be410e21c..deaadf1c2 100644 --- a/scripts/dune +++ b/scripts/dune @@ -37,6 +37,100 @@ communicate-with-javascript.t communicate-with-javascript.md.processed))) +(rule + (deps ../docs/language-concepts.md) + (target language-concepts.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff language-concepts.t language-concepts.md.processed))) + +(rule + (deps ../docs/data-types-and-runtime-rep.md) + (target data-types-and-runtime-rep.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff data-types-and-runtime-rep.t data-types-and-runtime-rep.md.processed))) + +(rule + (deps ../docs/attributes-and-extension-nodes.md) + (target attributes-and-extension-nodes.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff + attributes-and-extension-nodes.t + attributes-and-extension-nodes.md.processed))) + +(rule + (deps ../docs/working-with-js-objects-and-values.md) + (target working-with-js-objects-and-values.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff + working-with-js-objects-and-values.t + working-with-js-objects-and-values.md.processed))) + +(rule + (deps ../docs/advanced-js-interop.md) + (target advanced-js-interop.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff advanced-js-interop.t advanced-js-interop.md.processed))) + +(rule + (deps ../docs/bindings-cookbook.md) + (target bindings-cookbook.md.processed) + (action + (with-stdout-to + %{target} + (with-stdin-from + %{deps} + (run %{bin:extract_code_blocks}))))) + +(rule + (alias extract-code-blocks) + (action + (diff bindings-cookbook.t bindings-cookbook.md.processed))) + (rule (deps ../docs/build-system.md) (target build-system.md.processed) diff --git a/scripts/language-concepts.t b/scripts/language-concepts.t new file mode 100644 index 000000000..864c4193d --- /dev/null +++ b/scripts/language-concepts.t @@ -0,0 +1,164 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > let sum_sq = + > [ 1; 2; 3 ] + > |. Belt.List.map String.cat + > |. sum + > EOF + + $ dune build @melange + File "input.ml", line 4, characters 5-8: + 4 | |. sum + ^^^ + Error: Unbound value sum + [1] + + $ cat > input.ml <<\EOF + > let sum_sq = + > [ 1; 2; 3 ] + > |. Belt.List.map square + > |. sum + > EOF + + $ dune build @melange + File "input.ml", line 4, characters 5-8: + 4 | |. sum + ^^^ + Error: Unbound value sum + [1] + + $ cat > input.ml <<\EOF + > let sum_sq = + > [ 1; 2; 3 ] + > |> List.map String.cat + > |> sum + > EOF + + $ dune build @melange + File "input.ml", line 4, characters 5-8: + 4 | |> sum + ^^^ + Error: Unbound value sum + [1] + + $ cat > input.ml <<\EOF + > let sum = List.fold_left ( + ) 0 + > + > let sum_sq = + > [ 1; 2; 3 ] + > |> List.map square (* [1; 4; 9] *) + > |> sum (* 1 + 4 + 9 *) + > EOF + + $ dune build @melange + File "input.ml", line 5, characters 14-20: + 5 | |> List.map square (* [1; 4; 9] *) + ^^^^^^ + Error: Unbound value square + [1] + + $ cat > input.ml <<\EOF + > let ten = 3 |> square |> succ + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 15-21: + 1 | let ten = 3 |> square |> succ + ^^^^^^ + Error: Unbound value square + [1] + + $ cat > input.ml <<\EOF + > let ten = succ (square 3) + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 16-22: + 1 | let ten = succ (square 3) + ^^^^^^ + Error: Unbound value square + [1] + + $ cat > input.ml <<\EOF + > let square x = x * x + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let ( |> ) f g = g f + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type document + > + > external document : document = "document" + > external set_title : document -> string -> unit = "title" [@@mel.set] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type document + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type foo = string + > type bar = int + > external danger_zone : foo -> bar = "%identity" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external my_c_function : int -> string = "someCFunctionName" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type document + > external setTitleDom : document -> string -> unit = "title" [@@mel.set] + > + > type t = { + > age : int; [@mel.as "a"] + > name : string; [@mel.as "n"] + > } + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type name = + > | Name of string [@@unboxed] + > let student_name = Name "alice" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > [%%mel.raw "var a = 1; var b = 2"] + > let add = [%mel.raw "a + b"] + > EOF + + $ dune build @melange + diff --git a/scripts/working-with-js-objects-and-values.t b/scripts/working-with-js-objects-and-values.t new file mode 100644 index 000000000..63db5523f --- /dev/null +++ b/scripts/working-with-js-objects-and-values.t @@ -0,0 +1,500 @@ +This test file is automatically generated from its corresponding markdown +file. To update the tests, run `dune build @extract-code-blocks`. + + $ cat > dune-project < (lang dune 3.8) + > (using melange 0.1) + > EOF + + $ cat > dune < (melange.emit + > (emit_stdlib false) + > (target output) + > (libraries melange.belt melange.dom melange.node) + > (preprocess (pps melange.ppx))) + > EOF + + $ cat > input.ml <<\EOF + > type element + > type document + > external get_by_id : document -> string -> element option = "getElementById" + > [@@mel.send] [@@mel.return nullable] + > + > let test document = + > let elem = get_by_id document "header" in + > match elem with + > | None -> 1 + > | Some _element -> 2 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type x + > external x : x = "x" + > external set_onload : x -> ((x -> int -> unit)[@mel.this]) -> unit = "onload" + > [@@mel.set] + > external resp : x -> int = "response" [@@mel.get] + > let _ = + > set_onload x + > begin + > fun [@mel.this] o v -> Js.log (resp o + v) + > end + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add x y = x + y + > let _ = map [||] [||] add + > EOF + + $ dune build @melange + File "input.ml", line 2, characters 8-11: + 2 | let _ = map [||] [||] add + ^^^ + Error: Unbound value map + Hint: Did you mean max? + [1] + + $ cat > input.ml <<\EOF + > external map : + > 'a array -> 'b array -> (('a -> 'b -> 'c)[@mel.uncurry]) -> 'c array = "map" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add = fun [@u] x y -> x + y + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add x y = x + y + > let _ = map [||] [||] add + > EOF + + $ dune build @melange + File "input.ml", line 2, characters 8-11: + 2 | let _ = map [||] [||] add + ^^^ + Error: Unbound value map + Hint: Did you mean max? + [1] + + $ cat > input.ml <<\EOF + > external map : 'a array -> 'b array -> (('a -> 'b -> 'c)[@u]) -> 'c array + > = "map" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add x = let partial y = x + y in partial + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external map : 'a array -> 'b array -> ('a -> 'b -> 'c) -> 'c array = "map" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let add x y = x + y + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external process_on_exit : (_[@mel.as "exit"]) -> (int -> unit) -> unit + > = "process.on" + > + > let () = + > process_on_exit (fun exit_code -> + > Js.log ("error code: " ^ string_of_int exit_code)) + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type readline + > + > external on : + > readline -> + > ([ `close of unit -> unit | `line of string -> unit ][@mel.string]) -> + > readline = "on" + > [@@mel.send] + > + > let register rl = + > rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) + > EOF + + $ dune build @melange + File "input.ml", line 10, characters 24-29: + 10 | rl |. on (`close (fun event -> ())) |. on (`line (fun line -> Js.log line)) + ^^^^^ + Error (warning 27 [unused-var-strict]): unused variable event. + [1] + + $ cat > input.ml <<\EOF + > external test_int_type : + > ([ `on_closed | `on_open [@mel.as 20] | `in_bin ][@mel.int]) -> int + > = "testIntType" + > + > let value = test_int_type `on_open + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type document + > type style + > + > external document : document = "document" + > external get_by_id : document -> string -> Dom.element = "getElementById" + > [@@mel.send] + > external style : Dom.element -> style = "style" [@@mel.get] + > external transition_timing_function : + > style -> + > ([ `ease + > | `easeIn [@mel.as "ease-in"] + > | `easeOut [@mel.as "ease-out"] + > | `easeInOut [@mel.as "ease-in-out"] + > | `linear ] + > [@mel.string]) -> + > unit = "transitionTimingFunction" + > [@@mel.set] + > + > let element_style = style (get_by_id document "my-id") + > let () = transition_timing_function element_style `easeIn + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external read_file_sync : + > name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" + > [@@mel.module "fs"] + > + > let _ = read_file_sync ~name:"xx.txt" `ascii + > EOF + + $ dune build @melange + File "input.ml", line 2, characters 18-36: + 2 | name:string -> ([ `utf8 | `ascii ][@mel.string]) -> string = "readFileSync" + ^^^^^^^^^^^^^^^^^^ + Alert redundant: [@mel.string] is redundant here, you can safely remove it + + $ cat > input.ml <<\EOF + > external padLeft: + > string + > -> ([ `Str of string + > | `Int of int + > ] [@mel.unwrap]) + > -> string + > = "padLeft" + > + > let _ = padLeft "Hello World" (`Int 4) + > let _ = padLeft "Hello World" (`Str "Message from Melange: ") + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external drawCat : unit -> unit = "draw" [@@mel.module "MyGame"] + > external drawDog : giveName:string -> unit = "draw" [@@mel.module "MyGame"] + > external draw : string -> useRandomAnimal:bool -> unit = "draw" + > [@@mel.module "MyGame"] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type hide = Hide : 'a -> hide [@@unboxed] + > + > external join : hide array -> string = "join" [@@mel.module "path"] [@@mel.variadic] + > + > let v = join [| Hide "a"; Hide 2 |] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external join : string array -> string = "join" + > [@@mel.module "path"] [@@mel.variadic] + > let v = join [| "a"; "b" |] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for the `document` global *) + > type document + > + > external document : document = "document" + > external get_by_id : string -> Dom.element = "getElementById" + > [@@mel.send.pipe: document] + > external get_by_classname : string -> Dom.element = "getElementsByClassName" + > [@@mel.send.pipe: Dom.element] + > + > let el = document |> get_by_id "my-id" |> get_by_classname "my-class" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for the `document` global *) + > type document + > + > external document : document = "document" + > external get_by_id : document -> string -> Dom.element = "getElementById" + > [@@mel.send] + > external get_by_classname : Dom.element -> string -> Dom.element + > = "getElementsByClassName" + > [@@mel.send] + > + > let el = document |. get_by_id "my-id" |. get_by_classname "my-class" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for the `document` global *) + > type document + > + > external document : document = "document" + > external get_by_id : string -> Dom.element = "getElementById" + > [@@mel.send.pipe: document] + > + > let el = get_by_id "my-id" document + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for the `document` global *) + > type document + > + > external document : document = "document" + > external get_by_id : document -> string -> Dom.element = "getElementById" + > [@@mel.send] + > + > let el = get_by_id document "my-id" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" + > [@@mel.module "MyGame"] + > let () = draw ~x:10 ~y:20 () + > let () = draw ~y:20 ~x:10 () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external draw : x:int -> y:int -> ?border:bool -> unit -> unit = "draw" + > [@@mel.module "MyGame"] + > + > let () = draw ~x:10 ~y:20 ~border:true () + > let () = draw ~x:10 ~y:20 () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t + > + > external create : unit -> t = "GUI" + > [@@mel.new] [@@mel.scope "default"] [@@mel.module "dat.gui"] + > + > let gui = create () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > external imul : int -> int -> int = "imul" [@@mel.scope "Math"] + > + > let res = imul 1 2 + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t + > + > external back : t = "back" + > [@@mel.module "expo-camera"] [@@mel.scope "Camera", "Constants", "Type"] + > + > let camera_type_back = back + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type param + > external executeCommands : string -> param array -> unit = "" + > [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] + > + > let f a b c = executeCommands "hi" [| a; b; c |] + > EOF + + $ dune build @melange + File "input.ml", lines 2-3, characters 0-67: + 2 | external executeCommands : string -> param array -> unit = "" + 3 | [@@mel.scope "commands"] [@@mel.module "vscode"] [@@mel.variadic] + Alert fragile: executeCommands : the external name is inferred from val name is unsafe from refactoring when changing value name + + $ cat > input.ml <<\EOF + > external dirname : string -> string = "dirname" [@@mel.module "path"] + > let root = dirname "/User/github" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for `document` *) + > type document + > + > external document : document = "document" + > let document = document + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for `timeoutId` *) + > type timeoutId + > external setTimeout : (unit -> unit) -> int -> timeoutId = "setTimeout" + > external clearTimeout : timeoutId -> unit = "clearTimeout" + > + > let id = setTimeout (fun () -> Js.log "hello") 100 + > let () = clearTimeout id + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t + > external book : unit -> t = "Book" [@@mel.new] [@@mel.module] + > let myBook = book () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t + > external create_date : unit -> t = "Date" [@@mel.new] + > let date = create_date () + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t + > external create : int -> t = "Int32Array" [@@mel.new] + > external get : t -> int -> int = "" [@@mel.get_index] + > external set : t -> int -> int -> unit = "" [@@mel.set_index] + > + > let () = + > let i32arr = (create 3) in + > set i32arr 0 42; + > Js.log (get i32arr 0) + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > (* Abstract type for the `document` value *) + > type document + > + > external document : document = "document" + > + > external set_title : document -> string -> unit = "title" [@@mel.set] + > external get_title : document -> string = "title" [@@mel.get] + > + > let current = get_title document + > let () = set_title document "melange" + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () + > EOF + + $ dune build @melange + File "input.ml", line 1, characters 16-21: + 1 | let homeRoute = route ~_type:"GET" ~path:"/" ~action:(fun _ -> Js.log "Home") () + ^^^^^ + Error: Unbound value route + [1] + + $ cat > input.ml <<\EOF + > external route : + > _type:string -> + > path:string -> + > action:(string list -> unit) -> + > ?options:< .. > -> + > unit -> + > _ = "" + > [@@mel.obj] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let name_extended obj = obj##name ^ " wayne" + > + > let one = name_extended [%mel.obj { name = "john"; age = 99 }] + > let two = name_extended [%mel.obj { name = "jane"; address = "1 infinite loop" }] + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > let john = [%mel.obj { name = "john"; age = 99 }] + > let t = john##name + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type t = { + > foo : int; [@mel.as "0"] + > bar : string; [@mel.as "1"] + > } + > + > let value = { foo = 7; bar = "baz" } + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type action = { + > type_ : string [@mel.as "type"] + > } + > + > let action = { type_ = "ADD_USER" } + > EOF + + $ dune build @melange + + $ cat > input.ml <<\EOF + > type person = { + > name : string; + > friends : string array; + > age : int; + > } + > + > external john : person = "john" [@@mel.module "MySchool"] + > let john_name = john.name + > EOF + + $ dune build @melange +