# ReScript Language Overview Focused documentation for ReScript syntax, data types, control flow, modules, and core language features. # Language Overview A concise reference of ReScript's syntax and core language features. If you already know JavaScript and want a quick syntax guide first, see [ReScript for JavaScript Developers](./rescript-for-javascript-developers.mdx). ## Semicolons ReScript does not require semicolons. Line breaks are sufficient to separate statements. ## Comments | Syntax | Purpose | | -------------------------------- | ---------------------- | | `// Line comment` | Single-line comment | | `/* Block comment */` | Multi-line comment | | `/** Doc comment */` | Documentation comment | | `/*** Standalone doc comment */` | Standalone doc comment | ## Variables | Syntax | Description | | ------------------------------------- | ----------------------- | | `let x = 5` | Immutable binding | | `let x = ref(5); x := x.contents + 1` | Mutable value via `ref` | ## Strings | Syntax | Description | | ------------------------- | -------------------- | | `"Hello world!"` | String literal | | `"hello " ++ "world"` | String concatenation | | `` `hello ${message}` `` | String interpolation | | `` sql`select ${col};` `` | Tagged template | Strings must use double quotes (`"`). ## Booleans | Syntax | Description | | -------------------- | ------------------------------ | | `true`, `false` | Boolean literals | | `!` | Logical NOT | | `\|\|`, `&&` | Logical OR, AND | | `<=`, `>=`, `<`, `>` | Comparison operators | | `===`, `!==` | Referential (shallow) equality | | `==`, `!=` | Structural (deep) equality | There is no equality with implicit type casting. ## Numbers | Syntax | Description | | ------------ | ---------------------------------- | | `3` | Integer literal | | `3.1415` | Float literal | | `3 + 4` | Addition (works for int and float) | | `2 / 3 * 4` | Division and multiplication | | `2.0 ** 3.0` | Exponentiation | | `5 % 3` | Modulo | Arithmetic operators (`+`, `-`, `*`, `/`, `%`, `**`) work for both `int` and `float`. ## Records Records are typed, immutable-by-default data structures with named fields. | Syntax | Description | | --------------------------------------- | -------------------- | | `type point = {x: int, mutable y: int}` | Type declaration | | `{x: 30, y: 20}` | Record creation | | `point.x` | Field access | | `point.y = 30` | Mutable field update | | `{...point, x: 30}` | Immutable update | ## Arrays | Syntax | Description | | ----------------- | ---------------------- | | `[1, 2, 3]` | Array literal | | `myArray[1] = 10` | Mutable element update | Arrays are homogeneous. For mixed types, use tuples or [Untagged Variants](./variant.mdx#untagged-variants). ## Tuples | Syntax | Description | | ------------------- | ------------------- | | `(1, "Bob", true)` | Tuple literal | | `let (a, b, c) = t` | Tuple destructuring | Tuples are fixed-length, heterogeneous, and immutable. ## Null & Option ReScript has no `null` or `undefined`. The `option` type represents the possible absence of a value: | Syntax | Description | | ------------ | --------------- | | `None` | No value | | `Some("hi")` | A present value | ## Functions | Syntax | Description | | ----------------------------- | -------------------- | | `arg => retVal` | Anonymous function | | `let named = (arg) => retVal` | Named function | | `add(4, add(5, 6))` | Function application | ## Async / Await | Syntax | Description | | ---------------------------------- | ------------------------------ | | `async (arg) => {...}` | Async anonymous function | | `let named = async (arg) => {...}` | Async named function | | `await somePromise` | Await a promise | | `async (arg): string => {...}` | Typed async (return type only) | ## Blocks The last expression in a `{}` block is implicitly returned, including in function bodies.
Example Description
``` let myFun = (x, y) => { let doubleX = x + x let doubleY = y + y doubleX + doubleY } ``` Function body with implicit return
``` let result = { let x = 23 let y = 34 x + y } ``` Block expression bound to a variable
## If-Else | Syntax | Description | | ------------------- | ------------------------------------------------------------------------ | | `if a {b} else {c}` | Conditional expression | | `a ? b : c` | Ternary expression | | `switch` | Pattern matching β€” [see full docs](./pattern-matching-destructuring.mdx) | Conditionals are expressions: `let result = if a {"hello"} else {"bye"}` ## Destructuring | Syntax | Description | | --------------------------- | ------------------------- | | `let {a, b} = data` | Record destructuring | | `let [a, b] = data` | Array destructuring \* | | `let {a: aa, b: bb} = data` | Destructuring with rename | \* The compiler warns if `data` might not be of length 2. ## Loops | Syntax | Description | | ---------------------------- | --------------- | | `for i in 0 to 10 {...}` | Ascending loop | | `for i in 10 downto 0 {...}` | Descending loop | | `while true {...}` | While loop | ## JSX | Syntax | Description | | ----------------------------------------- | ---------------------- | | `` | Props | | `` | Argument punning | | `` | Explicit boolean props | | `...children` | Children spread | ## Exceptions | Syntax | Description | | --------------------------------------------- | --------------------- | | `throw(SomeException(...))` | Raise an exception | | `try a catch { \| SomeException(err) => ...}` | Catch an exception \* | \* There is no `finally` clause. ## Compilation Output Reference A reference showing how common ReScript features compile to JavaScript. | Feature | ReScript | JavaScript Output | | ------------------------- | ------------------------------------ | ------------------------------------------ | | String | `"Hello"` | `"Hello"` | | String Interpolation | `` `Hello ${message}` `` | `"Hello " + message` | | Character (discouraged) | `'x'` | `120` (char code) | | Integer | `23`, `-23` | `23`, `-23` | | Float | `23.0`, `-23.0` | `23.0`, `-23.0` | | Addition | `23 + 1` | `23 + 1` | | Float Addition | `23.0 + 1.0` | `23.0 + 1.0` | | Division/Multiply | `2 / 23 * 1` | `2 / 23 * 1` | | Float Division/Multiply | `2.0 / 23.0 * 1.0` | `2.0 / 23.0 * 1.0` | | Float Exponentiation | `2.0 ** 3.0` | `2.0 ** 3.0` | | String Concatenation | `"Hello " ++ "World"` | `"Hello " + "World"` | | Comparison | `>`, `<`, `>=`, `<=` | `>`, `<`, `>=`, `<=` | | Boolean operation | `!`, `&&`, `\|\|` | `!`, `&&`, `\|\|` | | Shallow and deep Equality | `===`, `==` | `===`, `==` | | List (discouraged) | `list{1, 2, 3}` | `{hd: 1, tl: {hd: 2, tl: {hd: 3, tl: 0}}}` | | List Prepend | `list{a1, a2, ...oldList}` | `{hd: a1, tl: {hd: a2, tl: theRest}}` | | Array | `[1, 2, 3]` | `[1, 2, 3]` | | Record | `type t = {b: int}; let a = {b: 10}` | `var a = {b: 10}` | | Multiline Comment | `/* Comment here */` | Not in output | | Single line Comment | `// Comment here` | Not in output | _Note that this is a cleaned-up reference table; some examples' JavaScript output may differ slightly in practice._ # Let Binding A "let binding", in other languages, might be called a "variable declaration". `let` _binds_ values to names. They can be seen and referenced by code that comes _after_ them. ```res let greeting = "hello!" let score = 10 let newScore = 10 + score ``` ```js let newScore = 20; let greeting = "hello!"; let score = 10; export { greeting, score, newScore }; ``` Because these bindings are pure and known up front, the JS output can inline the calculation and emit `20` directly for `newScore`. ## Block Scope Bindings can be scoped through `{}`. ```res let message = { let part1 = "hello" let part2 = "world" part1 ++ " " ++ part2 } // `part1` and `part2` not accessible here! ``` ```js let message = "hello world"; export { message }; ``` The whole block is pure too, so the generated JS can collapse it to the final string literal. The value of the last line of a scope is implicitly returned. ### Design Decisions ReScript's `if`, `while` and functions all use the same block scoping mechanism. The code below works **not** because of some special "if scope"; but simply because it's the same scope syntax and feature you just saw: ```res nocheck if displayGreeting { let message = "Enjoying the docs so far?" Console.log(message) } // `message` not accessible here! ``` ```js if (displayGreeting) { console.log("Enjoying the docs so far?"); } ``` ## Bindings Are Immutable Let bindings are "immutable", aka "cannot change". This helps our type system deduce and optimize much more than other languages (and in turn, help you more). ## Binding Shadowing The above restriction might sound unpractical at first. How would you change a value then? Usually, 2 ways: The first is to realize that many times, what you want isn't to mutate a variable's value. For example, this JavaScript pattern: ```js var result = 0; result = calculate(result); result = calculateSomeMore(result); ``` ...is really just to comment on intermediate steps. You didn't need to mutate `result` at all! You could have just written this JS: ```js var result1 = 0; var result2 = calculate(result1); var result3 = calculateSomeMore(result2); ``` In ReScript, this obviously works too: ```res nocheck let result1 = 0 let result2 = calculate(result1) let result3 = calculateSomeMore(result2) ``` ```js var result1 = 0; var result2 = calculate(0); var result3 = calculateSomeMore(result2); ``` Additionally, reusing the same let binding name overshadows the previous bindings with the same name. So you can write this too: ```res nocheck let result = 0 let result = calculate(result) let result = calculateSomeMore(result) ``` ```js var result = calculate(0); var result$1 = calculateSomeMore(result); ``` (Though for the sake of clarity, we don't recommend this). As a matter of fact, even this is valid code: ```res let result = "hello" Console.log(result) // prints "hello" let result = 1 Console.log(result) // prints 1 ``` ```js console.log("hello"); console.log(1); let result = 1; export { result }; ``` The binding you refer to is whatever's the closest upward. No mutation here! If you need _real_ mutation, e.g. passing a value around, have it modified by many pieces of code, we provide a slightly heavier [mutation feature](./mutation.mdx). ## Private let bindings Private let bindings are introduced in the release [7.2](../../blog/archived/bucklescript-release-7-2.mdx). In the module system, everything is public by default, the only way to hide some values is by providing a separate signature to list public fields and their types: ```res module A: { let b: int } = { let a = 3 let b = 4 } ``` `%%private` gives you an option to mark private fields directly ```res module A = { %%private(let a = 3) let b = 4 } ``` `%%private` also applies to file level modules, so in some cases, users do not need to provide a separate interface file just to hide some particular values. Note interface files are still recommended as a general best practice since they give you better separate compilation units and also they're better for documentation. Still, `%%private` is useful in the following scenarios: - **Code generators.** Some code generators want to hide some values but it is sometimes very hard or time consuming for code generators to synthesize the types for public fields. - **Quick prototyping.** During prototyping, we still want to hide some values, but the interface file is not stable yet. `%%private` provides you such convenience. # Type Types are the highlight of ReScript! They are: - **Strong**. A type can't change into another type. In JavaScript, your variable's type might change when the code runs (aka at runtime). E.g. a `number` variable might change into a `string` sometimes. This is an anti-feature; it makes the code much harder to understand when reading or debugging. - **Static**. ReScript types are erased after compilation and don't exist at runtime. Never worry about your types dragging down performance. You don't need type info during runtime; we report all the information (especially all the type errors) during compile time. Catch the bugs earlier! - **Sound**. This is our biggest differentiator versus many other typed languages that compile to JavaScript. Our type system is guaranteed to **never** be wrong. Most type systems make a guess at the type of a value and show you a type in your editor that's sometime incorrect. We don't do that. We believe that a type system that is sometime incorrect can end up being dangerous due to expectation mismatches. - **Fast**. Many developers underestimate how much of their project's build time goes into type checking. Our type checker is one of the fastest around. - **Inferred**. You don't have to write down the types! ReScript can deduce them from their values. Yes, it might seem magical that we can deduce all of your program's types, without incorrectness, without your manual annotation, and do so quickly. Welcome to ReScript =). The following sections explore more of our type system. ## Inference This let-binding doesn't contain any written type: ```res let score = 10 let add = (a, b) => a + b ``` ```js function add(a, b) { return (a + b) | 0; } let score = 10; export { score, add }; ``` ReScript knows that `score` is an `int`, judging by the value `10`. This is called **inference**. Likewise, it also knows that the `add` function takes 2 `int`s and returns an `int`, judging from the `+` operator, which works on ints. ## Type Annotation But you can also optionally write down the type, aka annotate your value: ```res let score: int = 10 ``` ```js let score = 10; export { score }; ``` If the type annotation for `score` doesn't correspond to our inferred type for it, we'll show you an error during compilation time. We **won't** silently assume your type annotation is correct, unlike many other languages. You can also wrap any expression in parentheses and annotate it: ```res nocheck let myInt = 5 let myInt: int = 5 let myInt = (5: int) + (4: int) let add = (x: int, y: int) : int => x + y let drawCircle = (~radius as r: int): circleType => /* code here */ ``` ```js var myInt = 9; function add(x, y) { return (x + y) | 0; } function drawCircle(r) { /* code here */ } ``` Note: in the last line, `(~radius as r: int)` is a labeled argument. More on this in the [function](./function.mdx) page. ## Type Alias You can refer to a type by a different name. They'll be equivalent: ```res type scoreType = int let x: scoreType = 10 ``` ```js let x = 10; export { x }; ``` ## Type Parameter (Aka Generic) Types can accept parameters, akin to generics in other languages. The parameters' names **need** to start with `'`. The use-case of a parameterized type is to kill duplications. Before: ```res // this is a tuple of 3 items, explained next type intCoordinates = (int, int, int) type floatCoordinates = (float, float, float) let a: intCoordinates = (10, 20, 20) let b: floatCoordinates = (10.5, 20.5, 20.5) ``` ```js let a = [10, 20, 20]; let b = [10.5, 20.5, 20.5]; export { a, b }; ``` After: ```res type coordinates<'a> = ('a, 'a, 'a) let a: coordinates = (10, 20, 20) let b: coordinates = (10.5, 20.5, 20.5) ``` ```js let a = [10, 20, 20]; let b = [10.5, 20.5, 20.5]; export { a, b }; ``` Note that the above codes are just contrived examples for illustration purposes. Since the types are inferred, you could have just written: ```res let buddy = (10, 20, 20) ``` ```js let buddy = [10, 20, 20]; export { buddy }; ``` The type system infers that it's a `(int, int, int)`. Nothing else needed to be written down. Type arguments appear in many places. Our `array<'a>` type is such a type that requires a type parameter. ```res // inferred as `array` let greetings = ["hello", "world", "how are you"] ``` ```js let greetings = ["hello", "world", "how are you"]; export { greetings }; ``` If types didn't accept parameters, the standard library would need to define the types `arrayOfString`, `arrayOfInt`, `arrayOfTuplesOfInt`, etc. That'd be tedious. Types can receive many arguments, and be composable. {/* TODO: too early for this example */} ```res type result<'a, 'b> = | Ok('a) | Error('b) type myPayload = {data: string} type myPayloadResults<'errorType> = array> let payloadResults: myPayloadResults = [ Ok({data: "hi"}), Ok({data: "bye"}), Error("Something wrong happened!") ] ``` ```js let payloadResults = [ { TAG: "Ok", _0: { data: "hi", }, }, { TAG: "Ok", _0: { data: "bye", }, }, { TAG: "Error", _0: "Something wrong happened!", }, ]; export { payloadResults }; ``` ## Recursive Types Just like a function, a type can reference itself within itself using `rec`: ```res type rec person = { name: string, friends: array } ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ## Mutually Recursive Types Types can also be _mutually_ recursive through `and`: ```res type rec student = {taughtBy: teacher} and teacher = {students: array} ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ## Type Escape Hatch ReScript's type system is robust and does not allow dangerous, unsafe stuff like implicit type casting, randomly guessing a value's type, etc. However, out of pragmatism, we expose a single escape hatch for you to "lie" to the type system: ```res nocheck external myShadyConversion: myType1 => myType2 = "%identity" ``` ```js // Empty output ``` This declaration converts a `myType1` of your choice to `myType2` of your choice. You can use it like so: ```res external convertToFloat : int => float = "%identity" let age = 10 let gpa = 2.1 + convertToFloat(age) ``` ```js let gpa = 2.1 + 10; let age = 10; export { age, gpa }; ``` Obviously, do **not** abuse this feature. Use it tastefully when you're working with existing, overly dynamic JS code, for example. More on externals [here](./external.mdx). **Note**: this particular `external` is the only one that isn't preceded by a `@` [attribute](./attribute.mdx). # Primitive Types ReScript comes with the familiar primitive types like `string`, `int`, `float`, etc. {/* TODO: doc unit */} ## String ReScript `string`s are delimited using **double** quotes (single quotes are reserved for the character type below). ```res let greeting = "Hello world!" let multilineGreeting = "Hello world!" ``` ```js let greeting = "Hello world!"; let multilineGreeting = "Hello\n world!"; export { greeting, multilineGreeting }; ``` To concatenate strings, use `++`: ```res let greetings = "Hello " ++ "world!" ``` ```js let greetings = "Hello world!"; export { greetings }; ``` Since both sides of the concatenation are known, the JS output becomes a single string literal. ### String Interpolation There's a special syntax for string that allows - multiline string just like before - no special character escaping - Interpolation ```res let name = "Joe" let greeting = `Hello World πŸ‘‹ ${name} ` ``` ```js let name = "Joe"; let greeting = `Hello World πŸ‘‹ ` + name + ` `; export { name, greeting }; ``` This is just like JavaScript's backtick string interpolation, except without needing to escape special characters. ### Usage See the familiar `String` API in the [API docs](/docs/manual/api/stdlib/string). Since a ReScript string maps to a JavaScript string, you can mix & match the string operations in all standard libraries. ### Tips & Tricks **You have a good type system now!** In an untyped language, you'd often overload the meaning of string by using it as: - a unique id: `var BLUE_COLOR = "blue"` - an identifier into a data structure: `var BLUE = "blue" var RED = "red" var colors = [BLUE, RED]` - the name of an object field: `person["age"] = 24` - an enum: `if (audio.canPlayType() === 'probably') {...}` [(ΰ² _ΰ² )](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType#Return_value) - other crazy patterns you'll soon find horrible, after getting used to ReScript's alternatives. The more you overload the poor string type, the less the type system (or a teammate) can help you! ReScript provides concise, fast and maintainable types & data structures alternatives to the use-cases above (e.g. [variants](./variant.mdx)). ## Char ReScript has a type for a string with a single letter: ```res let firstLetterOfAlphabet = 'a' ``` ```js let firstLetterOfAlphabet = /* 'a' */ 97; export { firstLetterOfAlphabet }; ``` **Note**: Char doesn't support Unicode or UTF-8 and is therefore not recommended. To convert a String to a Char, use `String.get("a", 0)`. To convert a Char to a String, use `String.make(1, 'a')`. ## Regular Expression ReScript regular expressions compile cleanly to their JavaScript counterpart: ```res let r = /b/g ``` ```js let r = /b/g; export { r }; ``` A regular expression like the above has the type `RegExp.t`. The [RegExp](/docs/manual/api/stdlib/regexp) module contains the regular expression helpers you have seen in JS. ## Boolean A ReScript boolean has the type `bool` and can be either `true` or `false`. Common operations: - `&&`: logical and. - `||`: logical or. - `!`: logical not. - `<=`, `>=`, `<`, `>` - `==`: structural equal, compares data structures deeply. `(1, 2) == (1, 2)` is `true`. Convenient, but use with caution. - `===`: referential equal, compares shallowly. `(1, 2) === (1, 2)` is `false`. `let myTuple = (1, 2); myTuple === myTuple` is `true`. - `!=`: structural unequal. - `!==`: referential unequal. ReScript's `true/false` compiles into a JavaScript `true/false`. ## Integers 32-bits, truncated when necessary. We provide the usual operations on them: `+`, `-`, `*`, `/`, etc. See [Int](/docs/manual/api/stdlib/int) for helper functions. Integer operators include `+`, `-`, `*`, `/`, `**`, and `%`. `%` keeps the familiar `mod` naming, but its semantics are remainder (same behavior as JavaScript `%`). Bitwise operators for `int`: `~~~`, `&&&`, `|||`, `^^^`, `<<`, `>>`, `>>>`. **Be careful when you bind to JavaScript numbers!** Since ReScript integers have a much smaller range than JavaScript numbers, data might get lost when dealing with large numbers. In those cases it’s much safer to bind the numbers as **float**. Be extra mindful of this when binding to JavaScript Dates and their epoch time. To improve readability, you may place underscores in the middle of numeric literals such as `1_000_000`. Note that underscores can be placed anywhere within a number, not just every three digits. ## Floats Arithmetic operators (`+`, `-`, `*`, `/`, `%`, `**`) work for both `int` and `float`. Like `0.5 + 0.6`. See [Float](/docs/manual/api/stdlib/float) for helper functions. As with integers, you may use underscores within literals to improve readability. ### Int-to-Float Coercion `int` values can be coerced to `float` with the `:>` (type coercion) operator. ```res let result = (1 :> float) + 2. ``` ```js let result = 1 + 2; export { result }; ``` ## Big Integers (experimental) **Since 11.1** For values which are too large to be represented by Int or Float, there is the `bigint` primitive type. We provide the usual operations on them: `+`, `-`, `*`, `/`, `**`, `%`, etc. See [BigInt](/docs/manual/api/stdlib/bigint) for helper functions. A `bigint` number is denoted by a trailing `n` like so: `42n`. As `bigint` is a different data type than `int`, it's necessary to open the corresponding module to overload the operators. ```res open! BigInt let a = 9007199254740991n + 9007199254740991n let b = 2n ** 2n ``` ```js let a = 9007199254740991n + 9007199254740991n; let b = 2n ** 2n; export { a, b }; ``` It also supports all the bitwise operations, except unsigned shift right (`>>>`), which is not supported by JS itself for `bigint`s. ```res open! BigInt let a = 5n &&& 3n let b = 5n ||| 2n let c = 5n ^^^ 1n let d = ~~~5n let e = 5n << 1n let f = 5n >> 1n ``` ```js let a = 5n & 3n; let b = 5n | 2n; let c = 5n ^ 1n; let d = ~5n; let e = 5n << 1n; let f = 5n >> 1n; export { a, b, c, d, e, f }; ``` It can also be pattern-matched. ```res let bigintValue = 1n switch bigintValue { | 1n => Console.log("Small bigint") | 100n => Console.log("Larger bigint") | _ => Console.log("Other bigint") } ``` ```js console.log("Small bigint"); let bigintValue = 1n; export { bigintValue }; ``` ## Unit The `unit` type indicates the absence of a specific value. It has only a single value, `()`, which acts as a placeholder when no other value exists or is needed. It compiles to JavaScript's `undefined` and resembles the `void` type in languages such as C++. What's the point of such a type? Consider the `Math.random` function. Its type signature is `unit => float`, which means it receives a `unit` as input and calculates a random `float` as output. You use the function like this - `let x = Math.random()`. Notice `()` as the first and only function argument. Imagine a simplified `Console.log` function that prints a message. Its type signature is `string => unit` and you'd use it like this `Console.log("Hello!")`. It takes a string as input, prints it, and then returns nothing useful. When `unit` is the output of a function it means the function performs some kind of side-effect. ## Unknown The `unknown` type represents values with contents that are a mystery or are not 100% guaranteed to be what you think they are. It provides type-safety when interacting with data received from an untrusted source. For example, suppose an external function is supposed to return a `string`. It might. But if the documentation is not accurate or the code has bugs, the function could return `null`, an `array`, or something else you weren't expecting. The ReScript type system helps you avoid run-time crashes and unpredicatable behavior by preventing you from using `unknown` in places that expect a `string` or `int` or some other type. The ReScript core libraries also provide utility functions to help you inspect `unknown` values and access their contents. In some cases you may need a JSON parsing library to convert `unknown` values to types you can safely use. Consider using `unknown` when receiving data from [external JavaScript functions](./bind-to-js-function.mdx) # Tuple Tuples are a ReScript-specific data structure that don't exist in JavaScript. They are: - immutable - ordered - fix-sized at creation time - heterogeneous (can contain different types of values) ```res let ageAndName = (24, "Lil' ReScript") let my3dCoordinates = (20.0, 30.5, 100.0) ``` ```js let ageAndName = [24, "Lil' ReScript"]; let my3dCoordinates = [20.0, 30.5, 100.0]; export { ageAndName, my3dCoordinates }; ``` Tuples' types can be used in type annotations as well. Tuple types visually resemble tuples values. ```res prelude let ageAndName: (int, string) = (24, "Lil' ReScript") // a tuple type alias type coord3d = (float, float, float) let my3dCoordinates: coord3d = (20.0, 30.5, 100.0) ``` ```js var ageAndName = [24, "Lil' ReScript"]; var my3dCoordinates = [20.0, 30.5, 100.0]; ``` **Note**: there's no tuple of size 1. You'd just use the value itself. ## Usage To get a specific member of a tuple, destructure it: ```res let (_, y, _) = my3dCoordinates // now you've retrieved y ``` ```js let ageAndName = [24, "Lil' ReScript"]; let my3dCoordinates = [20.0, 30.5, 100.0]; let y = 30.5; export { ageAndName, my3dCoordinates, y }; ``` The `_` means you're ignoring the indicated members of the tuple. Tuples aren't meant to be updated mutatively. You'd create new ones by destructuring the old ones: ```res let coordinates1 = (10, 20, 30) let (c1x, _, _) = coordinates1 let coordinates2 = (c1x + 50, 20, 30) ``` ```js let coordinates2 = [60, 20, 30]; let ageAndName = [24, "Lil' ReScript"]; let my3dCoordinates = [20.0, 30.5, 100.0]; let coordinates1 = [10, 20, 30]; let c1x = 10; export { ageAndName, my3dCoordinates, coordinates1, c1x, coordinates2 }; ``` ## Tips & Tricks You'd use tuples in handy situations that pass around multiple values without too much ceremony. For example, to return many values: ```res nocheck let getCenterCoordinates = () => { let x = doSomeOperationsHere() let y = doSomeMoreOperationsHere() (x, y) } ``` ```js function getCenterCoordinates(param) { var x = doSomeOperationsHere(undefined); var y = doSomeMoreOperationsHere(undefined); return [x, y]; } ``` Try to keep the usage of tuple **local**. For data structures that are long-living and passed around often, prefer a **record**, which has named fields. # Record Records are like JavaScript objects but: - are immutable by default - have fixed fields (not extensible) ## Type Declaration A record needs a mandatory type declaration: ```res prelude type person = { age: int, name: string, } ``` ```js // Empty output ``` You can also nest definitions of records. ```res type nestedPerson = { age: int, name: string, notificationSettings: { sendEmails: bool, allowPasswordLogin: bool, }, } let person = { age: 90, name: "Test Person", notificationSettings: { sendEmails: true, allowPasswordLogin: false, }, } ``` ```js let person = { age: 90, name: "Test Person", notificationSettings: { sendEmails: true, allowPasswordLogin: false, }, }; export { person }; ``` Nesting record definitions is a nice way to group records that are part of the same structure, and won't be referenced from the outside. If you end up needing to refer to a nested record type explicitly, you should make it an explicit definition instead of a nested one. This is mainly for 2 reasons: - The records that are automatically generated for the nested record definitions are named in a way that would require you to use escaped identifiers to reference them. The nested record at `notificationSettings` above would be named `\"person.notificationSettings"` for instance - For the sake of clarity (and caring about your co-workers), having an explicit and named definition to look at and refer to is much easier than scanning a potentially large record definition for the nested record you're looking for So if we in the example above ended up needing to refer to `person.notificationSettings` nested record from the outside, we should instead make it explicit, just like how we normally define records: ```res type personNotificationSettings = { sendEmails: bool, allowPasswordLogin: bool, } type explicitPerson = { age: int, name: string, notificationSettings: personNotificationSettings } let person = { age: 90, name: "Test Person", notificationSettings: { sendEmails: true, allowPasswordLogin: false, }, } ``` ```js let person = { age: 90, name: "Test Person", notificationSettings: { sendEmails: true, allowPasswordLogin: false, }, }; export { person }; ``` ## Creation To create a `person` record (declared above): ```res prelude let me = { age: 5, name: "Big ReScript" } ``` ```js var me = { age: 5, name: "Big ReScript", }; ``` When you create a new record value, ReScript tries to find a record type declaration that conforms to the shape of the value. So the `me` value here is inferred as of type `person`. The type is found by looking above the `me` value. **Note**: if the type instead resides in another file or module, you need to explicitly indicate which file or module it is: ```res // School.res type schoolPerson = {age: int, name: string} ``` ```js let me = { age: 5, name: "Big ReScript", }; export { me }; ``` ```res nocheck // Example.res let me: School.schoolPerson = {age: 20, name: "Big ReScript"} /* or */ let me2 = {School.age: 20, name: "Big ReScript"} ``` ```js var me = { age: 20, name: "Big ReScript", }; var me2 = { age: 20, name: "Big ReScript", }; ``` In both `me` and `me2` the record definition from `School` is found. The first one, `me` with the regular type annotation, is preferred. ## Access Use the familiar dot notation: ```res let name = me.name ``` ```js let me = { age: 5, name: "Big ReScript", }; let name = "Big ReScript"; export { me, name }; ``` ## Immutable Update New records can be created from old records with the `...` spread operator. The original record isn't mutated. ```res let meNextYear = {...me, age: me.age + 1} ``` ```js let meNextYear = { age: 6, name: "Big ReScript", }; let me = { age: 5, name: "Big ReScript", }; export { me, meNextYear }; ``` **Note**: spread cannot add new fields to the record value, as a record's shape is fixed by its type. ## Mutable Update Record fields can optionally be mutable. This allows you to efficiently update those fields in-place with the `=` operator. ```res type mutablePerson = { name: string, mutable age: int } let baby: mutablePerson = {name: "Baby ReScript", age: 5} baby.age = baby.age + 1 // `baby.age` is now 6. Happy birthday! ``` ```js let baby = { name: "Baby ReScript", age: 5, }; baby.age = (baby.age + 1) | 0; let me = { age: 5, name: "Big ReScript", }; export { me, baby }; ``` Fields not marked with `mutable` in the type declaration cannot be mutated. ## JavaScript Output ReScript records compile to straightforward JavaScript objects; see the various JS output tabs above. ## Optional Record Fields ReScript [`v10`](../../blog/release-10-0-0.mdx#experimental-optional-record-fields) introduced optional record fields. This means that you can define fields that can be omitted when creating the record. It looks like this: ```res type optionalPerson = { age: int, name?: string } ``` ```js let me = { age: 5, name: "Big ReScript", }; export { me }; ``` Notice how `name` has a suffixed `?`. That means that the field itself is _optional_. ### Creation You can omit any optional fields when creating a record. Not setting an optional field will default the field's value to `None`: ```res type optionalPerson = { age: int, name?: string } let me: optionalPerson = { age: 5, name: "Big ReScript" } let friend: optionalPerson = { age: 7 } ``` ```js let me = { age: 5, name: "Big ReScript", }; let friend = { age: 7, }; export { me, friend }; ``` This has consequences for pattern matching, which we'll expand a bit on soon. ## Immutable Update Updating an optional field via an immutable update above lets you set that field value without needing to care whether it's optional or not. ```res type personWithOptionalName = { age: int, name?: string } let me: personWithOptionalName = { age: 123, name: "Hello" } let withoutName = { ...me, name: "New Name" } ``` ```js let me = { age: 123, name: "Hello", }; let newrecord = { ...me }; newrecord.name = "New Name"; let withoutName = newrecord; export { me, withoutName }; ``` However, if you want to set the field to an optional value, you prefix that value with `?`: ```res type personWithOptionalName = { age: int, name?: string } let me: personWithOptionalName = { age: 123, name: "Hello" } let maybeName = Some("My Name") let withoutName = { ...me, name: ?maybeName } ``` ```js let me = { age: 123, name: "Hello", }; let maybeName = "My Name"; let newrecord = { ...me }; newrecord.name = maybeName; let withoutName = newrecord; export { me, maybeName, withoutName }; ``` You can unset an optional field's value via that same mechanism by setting it to `?None`. ### Pattern Matching on Optional Fields [Pattern matching](./pattern-matching-destructuring.mdx), one of ReScript's most important features, has two caveats when you deal with optional fields. When matching on the value directly, it's an `option`. Example: ```res type matchedOptionalPerson = { age: int, name?: string, } let me: matchedOptionalPerson = { age: 123, name: "Hello", } let isRescript = switch me.name { | Some("ReScript") => true | Some(_) | None => false } ``` ```js let isRescript = "Hello" === "ReScript"; let me = { age: 123, name: "Hello", }; export { me, isRescript }; ``` But, when matching on the field as part of the general record structure, it's treated as the underlying, non-optional value: ```res type matchedOptionalRecord = { age: int, name?: string, } let me: matchedOptionalRecord = { age: 123, name: "Hello", } let isRescript = switch me { | {name: "ReScript"} => true | _ => false } ``` ```js let isRescript = "Hello" === "ReScript"; let me = { age: 123, name: "Hello", }; export { me, isRescript }; ``` Sometimes you _do_ want to know whether the field was set or not. You can tell the pattern matching engine about that by prefixing your option match with `?`, like this: ```res type matchedOptionalPerson = { age: int, name?: string, } let me: matchedOptionalPerson = { age: 123, name: "Hello", } let nameWasSet = switch me { | {name: ?None} => false | {name: ?Some(_)} => true } ``` ```js let me = { age: 123, name: "Hello", }; let nameWasSet = true; export { me, nameWasSet }; ``` ## Record Type Spread In ReScript v11, you can now spread one or more record types into a new record type. It looks like this: ```rescript type a = { id: string, name: string, } type b = { age: int } type c = { ...a, ...b, active: bool } ``` `type c` will now be: ```rescript type c = { id: string, name: string, age: int, active: bool, } ``` Record type spreads act as a 'copy-paste' mechanism for fields from one or more records into a new record. This operation inlines the fields from the spread records directly into the new record definition, while preserving their original properties, such as whether they are optional or mandatory. It's important to note that duplicate field names are not allowed across the records being spread, even if the fields have the same type. ## Record Type Coercion Record type coercion gives us more flexibility when passing around records in our application code. In other words, we can now coerce a record `a` to be treated as a record `b` at the type level, as long as the original record `a` contains the same set of fields in `b`. Here's an example: ```rescript type a = { name: string, age: int, } type b = { name: string, age: int, } let nameFromB = (b: b) => b.name let a: a = { name: "Name", age: 35, } let name = nameFromB(a :> b) ``` Notice how we _coerced_ the value `a` to type `b` using the coercion operator `:>`. This works because they have the same record fields. This is purely at the type level, and does not involve any runtime operations. Additionally, we can also coerce records from `a` to `b` whenever `a` is a super-set of `b` (i.e. `a` containing all the fields of `b`, and more). The same example as above, slightly altered: ```rescript type a = { id: string, name: string, age: int, active: bool, } type b = { name: string, age: int, } let nameFromB = (b: b) => b.name let a: a = { id: "1", name: "Name", age: 35, active: true, } let name = nameFromB(a :> b) ``` Notice how `a` now has more fields than `b`, but we can still coerce `a` to `b` because `b` has a subset of the fields of `a`. In combination with [optional record fields](./record.mdx#optional-record-fields), one may coerce a mandatory field of an `option` type to an optional field: ```rescript type a = { name: string, // mandatory, but explicitly typed as option age: option, } type b = { name: string, // optional field age?: int, } let nameFromB = (b: b) => b.name let a: a = { name: "Name", age: Some(35), } let name = nameFromB(a :> b) ``` ## Tips & Tricks ### Record Types Are Found By Field Name With records, you **cannot** say "I'd like this function to take any record type, as long as they have the field `age`". The following **won't work as intended**: ```res nocheck type person = {age: int, name: string} type monster = {age: int, hasTentacles: bool} let getAge = (entity) => entity.age ``` ```js function getAge(entity) { return entity.age; } ``` Instead, `getAge` will infer that the parameter `entity` must be of type `monster`, the closest record type with the field `age`. The following code's last line fails: ```res nocheck let kraken = {age: 9999, hasTentacles: true} let me = {age: 5, name: "Baby ReScript"} getAge(kraken) getAge(me) // type error! ``` The type system will complain that `me` is a `person`, and that `getAge` only works on `monster`. If you need such capability, use ReScript objects, described [here](./object.mdx). ### Optional Fields in Records Can Be Useful for Bindings Many JavaScript APIs tend to have large configuration objects that can be a bit annoying to model as records, since you previously always needed to specify all record fields when creating a record. Optional record fields, introduced in [`v10`](../../blog/release-10-0-0.mdx#experimental-optional-record-fields), is intended to help with this. Optional fields will let you avoid having to specify all fields, and let you just specify the one's you care about. A significant improvement in ergonomics for bindings and other APIs with for example large configuration objects. ## Design Decisions Why use records instead of objects? 1. The truth is that most of the times in your app, your data's shape is actually fixed, and if it's not, it can potentially be better represented as a combination of variant (introduced next) + record instead. 2. Since a record type is resolved through finding that single explicit type declaration (we call this "nominal typing"), the type error messages end up better than the counterpart ("structural typing", like for tuples). This makes refactoring easier; changing a record type's fields naturally allows the compiler to know that it's still the same record, just misused in some places. Otherwise, under structural typing, it might get hard to tell whether the definition site or the usage site is wrong. # Object ReScript objects are like [records](./record.mdx), but: - No type declaration needed. - Structural and more polymorphic, [unlike records](./record.mdx#record-types-are-found-by-field-name). - Doesn't support updates unless the object comes from the JS side. - Doesn't support [pattern matching](./pattern-matching-destructuring.mdx). {/* TODO: support update man */} Although ReScript records compile to clean JavaScript objects, ReScript objects are a better candidate for emulating/binding to JS objects, as you'll see. ## Type Declaration **Optional**, unlike for records. The type of an object is inferred from the value, so you never really need to write down its type definition. Nevertheless, here's its type declaration syntax: ```res prelude type person = { "age": int, "name": string }; ``` ```js // Empty output ``` Visually similar to record type's syntax, with the field names quoted. {/* TODO: document {.} and {..} */} ## Creation To create a new object: ```res let me = { "age": 5, "name": "Big ReScript" } ``` ```js let me = { age: 5, name: "Big ReScript", }; export { me }; ``` **Note**: as said above, unlike for record, this `me` value does **not** try to find a conforming type declaration with the field `"age"` and `"name"`; rather, the type of `me` is inferred as `{"age": int, "name": string}`. This is convenient, but also means this code passes type checking without errors: ```res nocheck type person = { "age": int }; let me = { "age": "hello!" // age is a string. No error. } ``` ```js var me = { age: "hello!", }; ``` Since the type checker doesn't try to match `me` with the type `person`. If you ever want to force an object value to be of a predeclared object type, just annotate the value: ```res nocheck let me: person = { "age": "hello!" } ``` Now the type system will error properly. ## Access ```res nocheck let age = me["age"] ``` ```js var age = me["age"]; ``` ## Update Disallowed unless the object is a binding that comes from the JavaScript side. In that case, use `=` ```res type student = { @set "age": int, @set "name": string, } @module("MyJSFile") external student1: student = "student1" student1["name"] = "Mary" ``` ```js import * as MyJSFile from "MyJSFile"; MyJSFile.student1.name = "Mary"; ``` ## Combine Types You can spread one object type definition into another using `...`: ```res type point2d = { "x": float, "y": float, } type point3d = { ...point2d, "z": float, } let myPoint: point3d = { "x": 1.0, "y": 2.0, "z": 3.0, } ``` ```js let myPoint = { x: 1.0, y: 2.0, z: 3.0, }; export { myPoint }; ``` This only works with object types, not object values! ## Tips & Tricks Since objects don't require type declarations, and since ReScript infers all the types for you, you get to very quickly and easily (and dangerously) bind to any JavaScript API. Check the JS output tab: ```res // The type of document is just some random type 'a // that we won't bother to specify @val external document: 'a = "document" // call a method document["addEventListener"]("mouseup", _event => { Console.log("clicked!") }) // get a property let loc = document["location"] // set a property document["location"]["href"] = "rescript-lang.org" ``` ```js document.addEventListener("mouseup", (_event) => { console.log("clicked!"); }); let loc = document.location; document.location.href = "rescript-lang.org"; export { loc }; ``` The `external` feature and the usage of this trick are also documented in the [external](./external.mdx#tips--tricks) section later. It's an excellent way to start writing some ReScript code without worrying about whether bindings to a particular library exists. # Dictionary ReScript has first class support for dictionaries. Dictionaries are mutable objects with string keys, where all values must have the same type. Dicts compile to regular JavaScript objects at runtime. ## Create You can create a new dictionary in a few different ways, depending on your use case. ```res prelude // Using the first class dict syntax let d = dict{"A": 5, "B": 6} // Programatically via the standard library let d2 = Dict.fromArray([("A", 5), ("B", 6)]) ``` ```js let d = { A: 5, B: 6, }; let d2 = Object.fromEntries([ ["A", 5], ["B", 6], ]); ``` A few things to note here: - Using the first class `dict{}` syntax compiles cleanly to a JavaScript object directly - Using `Dict.fromArray` is useful when you need to create a dictionary programatically ## Access You can access values from a Dictionary either via the the standard library `Dict` module functions, or using pattern matching. ```res prelude let d = dict{"A": 5, "B": 6, "C": 7} // Using `Dict.get` let a = d->Dict.get("A") // Switching on the full dict let b = switch d { | dict{"B": b} => Some(b) | _ => None } // Destructuring let dict{"C": ?c} = d ``` ```js let d = { A: 5, B: 6, C: 7, }; let a = d["A"]; let b = d.B; let b$1 = b !== undefined ? b : undefined; let c = d.C; ``` > In the Destructuring example, we're using the `?` optional pattern match syntax to pull out the `C` key value as an optional, regardless of if the dict has it or not. ## Pattern matching Dictionaries have first class support for pattern matching. Read more in the [dedicated guide on pattern matching and destructring in ReScript](./pattern-matching-destructuring.mdx#match-on-dictionaries). ## Updating and setting values You can set and update new values on your dictionary using the `Dict.set` function. All updates are mutable. ```res prelude let d = dict{"A": 5, "B": 6} d->Dict.set("C", 7) ``` ```js let d = { A: 5, B: 6, }; d["C"] = 7; ``` ## Advanced example: Pattern matching on JSON JSON objects are represented as dictionaries (`dict`). You can leverage that fact to decode JSON in a nice way, using only language features: ```res prelude type user = { name: string, email: string, } /** Decode JSON to a `user`. */ let decodeUser = (json: JSON.t) => { switch json { | Object(dict{"name": JSON.String(name), "email": JSON.String(email)}) => Some({name, email}) | _ => None } } ``` ```js function decodeUser(json) { if (typeof json !== "object" || json === null || Array.isArray(json)) { return; } let name = json.name; if (typeof name !== "string") { return; } let email = json.email; if (typeof email === "string") { return { name: name, email: email, }; } } ``` # Variant So far, most of ReScript's data structures might look familiar to you. This section introduces an extremely important, and perhaps unfamiliar, data structure: variant. Most data structures in most languages are about "this **and** that". A variant allows us to express "this **or** that". ```res type myResponse = | Yes | No | PrettyMuch let areYouCrushingIt = Yes ``` ```js let areYouCrushingIt = "Yes"; export { areYouCrushingIt }; ``` `myResponse` is a variant type with the cases `Yes`, `No` and `PrettyMuch`, which are called "variant constructors" (or "variant tag"). The `|` bar separates each constructor. **Note**: a variant's constructors need to be capitalized. ## Variant Needs an Explicit Definition If the variant you're using is in a different file, bring it into scope [record](./record.mdx) type ```res // Zoo.res type animal = Dog | Cat | Bird ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ```res nocheck // Example.res let pet: Zoo.animal = Dog // preferred // or let pet2 = Zoo.Dog ``` ```js var pet = "Dog"; var pet2 = "Dog"; ``` ## Constructor Arguments A variant's constructors can hold extra data separated by comma. ```res prelude type account = | None | Instagram(string) | Facebook(string, int) ``` ```js // Empty output ``` Here, `Instagram` holds a `string`, and `Facebook` holds a `string` and an `int`. Usage: ```res let myAccount = Facebook("Josh", 26) let friendAccount = Instagram("Jenny") ``` ```js let myAccount = { TAG: "Facebook", _0: "Josh", _1: 26, }; let friendAccount = { TAG: "Instagram", _0: "Jenny", }; export { myAccount, friendAccount }; ``` ### Labeled Variant Payloads (Inline Record) If a variant payload has multiple fields, you can use a record-like syntax to label them for better readability: ```res type user = | Number(int) | Id({name: string, password: string}) let me = Id({name: "Joe", password: "123"}) ``` ```js let me = { TAG: "Id", name: "Joe", password: "123", }; export { me }; ``` This is technically called an "inline record", and only allowed within a variant constructor. You cannot inline a record type declaration anywhere else in ReScript. Of course, you can just put a regular record type in a variant too: ```res type u = {name: string, password: string} type user = | Number(int) | Id(u) let me = Id({name: "Joe", password: "123"}) ``` ```js let me = { TAG: "Id", _0: { name: "Joe", password: "123", }, }; export { me }; ``` The output is slightly uglier and less performant than the former. ## Variant Type Spreads Just like [with records](./record.mdx#record-type-spread), it's possible to use type spreads to create new variants from other variants: ```rescript type a = One | Two | Three type b = | ...a | Four | Five ``` Type `b` is now: ```rescript type b = One | Two | Three | Four | Five ``` Type spreads act as a 'copy-paste', meaning all constructors are copied as-is from `a` to `b`. Here are the rules for spreads to work: - You can't overwrite constructors, so the same constructor name can exist in only one place as you spread. This is true even if the constructors are identical. - All variants and constructors must share the same runtime configuration - `@unboxed`, `@tag`, `@as` and so on. - You can't spread types in recursive definitions. Note that you need a leading `|` if you want to use a spread in the first position of a variant definition. ### Pattern Matching On Variant See the [Pattern Matching/Destructuring](./pattern-matching-destructuring.mdx) section later. ## JavaScript Output A variant value compiles to 3 possible JavaScript outputs depending on its type declaration: - If the variant value is a constructor with no payload, it compiles to a string of the constructor name. Example: `Yes` compiles to `"Yes"`. - If it's a constructor with a payload, it compiles to an object with the field `TAG` and the field `_0` for the first payload, `_1` for the second payload, etc. The value of `TAG` is the constructor name as string by default, but note that the name of the `TAG` field as well as the string value used for each constructor name [can be customized](#tagged-variants). - Labeled variant payloads (the inline record trick earlier) compile to an object with the label names instead of `_0`, `_1`, etc. The object will have the `TAG` field as per the previous rule. Check the output in these examples: ```res type greeting = Hello | Goodbye let g1 = Hello let g2 = Goodbye type outcome = Good | Error(string) let o1 = Good let o2 = Error("oops!") type family = Child | Mom(int, string) | Dad (int) let f1 = Child let f2 = Mom(30, "Jane") let f3 = Dad(32) type person = Teacher | Student({gpa: float}) let p1 = Teacher let p2 = Student({gpa: 99.5}) type s = {score: float} type adventurer = Warrior(s) | Wizard(string) let a1 = Warrior({score: 10.5}) let a2 = Wizard("Joe") ``` ```js let g1 = "Hello"; let g2 = "Goodbye"; let o1 = "Good"; let o2 = { TAG: "Error", _0: "oops!", }; let f1 = "Child"; let f2 = { TAG: "Mom", _0: 30, _1: "Jane", }; let f3 = { TAG: "Dad", _0: 32, }; let p1 = "Teacher"; let p2 = { TAG: "Student", gpa: 99.5, }; let a1 = { TAG: "Warrior", _0: { score: 10.5, }, }; let a2 = { TAG: "Wizard", _0: "Joe", }; export { g1, g2, o1, o2, f1, f2, f3, p1, p2, a1, a2 }; ``` ## Tagged variants - The `@tag` attribute lets you customize the discriminator (default: `TAG`). - `@as` attributes control what each variant case is discriminated on (default: the variant case name as string). ### Example: Binding to TypeScript enums ```typescript // direction.ts /** Direction of the action. */ enum Direction { /** The direction is up. */ Up = "UP", /** The direction is down. */ Down = "DOWN", /** The direction is left. */ Left = "LEFT", /** The direction is right. */ Right = "RIGHT", } export const myDirection = Direction.Up; ``` You can bind to the above enums like so: ```rescript /** Direction of the action. */ type direction = | /** The direction is up. */ @as("UP") Up | /** The direction is down. */ @as("DOWN") Down | /** The direction is left. */ @as("LEFT") Left | /** The direction is right. */ @as("RIGHT") Right @module("./direction.js") external myDirection: direction = "myDirection" ``` Now, this maps 100% to the TypeScript code, including letting us bring over the documentation strings so we get a nice editor experience. ### String literals The same logic is easily applied to string literals from TypeScript, only here the benefit is even larger, because string literals have the same limitations in TypeScript that polymorphic variants have in ReScript: ```typescript // direction.ts type direction = "UP" | "DOWN" | "LEFT" | "RIGHT"; ``` There's no way to attach documentation strings to string literals in TypeScript, and you only get the actual value to interact with. ### Valid `@as` payloads Here's a list of everything you can put in the `@as` tag of a variant constructor: - A string literal: `@as("success")` - An int: `@as(5)` - A float: `@as(1.5)` - True/false: `@as(true)` and `@as(false)` - Null: `@as(null)` - Undefined: `@as(undefined)` ## Untagged variants With _untagged variants_ it is possible to mix types together that normally can't be mixed in the ReScript type system, as long as there's a way to discriminate them at runtime. For example, with untagged variants you can represent a heterogenous array: ```rescript @unboxed type listItemValue = String(string) | Boolean(bool) | Number(float) let myArray = [String("Hello"), Boolean(true), Boolean(false), Number(13.37)] ``` Here, each value will be _unboxed_ at runtime. That means that the variant payload will be all that's left, the variant case name wrapping the payload itself will be stripped out and the payload will be all that remains. It, therefore, compiles to this JS: ```javascript var myArray = ["hello", true, false, 13.37]; ``` In the above example, reaching back into the values is as simple as pattern matching on them. ### Advanced: Unboxing rules #### No overlap in constructors A variant can be unboxed if no constructors have overlap in their runtime representation. For example, you can't have `String1(string) | String2(string)` in the same unboxed variant, because there's no way for ReScript to know at runtime which of `String1` or `String2` that `string` belongs to, as it could belong to both. The same goes for two records - even if they have fully different shapes, they're still JavaScript `object` at runtime. Don't worry - the compiler will guide you and ensure there's no overlap. #### What you can unbox Here's a list of all possible things you can unbox: - `string`: `String(string)` - `float`: `Float(float)`. Note you can only have one of `float` or `int` because JavaScript only has `number` (not actually `int` and `float` like in ReScript) so we can't disambiguate between `float` and `int` at runtime. - `int`: `Int(int)`. See note above on `float`. - `bigint`: `BigInt(int)`. **Since 11.1** This is a distinct type from JavaScript's `number` type so you can use it beside either `float` or `int`. - `bool`: `Boolean(bool)` - `array<'value>`: `List(array)` - `('a, 'b, 'c)`: `Tuple((string, int, bool))`. Any size of tuples works, but you can have only one case of array or tuple in a variant. - `promise<'value>`: `Promise(promise)` - `Dict.t`: `Object(Dict.t)` - `Date.t`: `Date(Date.t)`. A JavaScript date. - `Blob.t`: `Blob(Blob.t)`. A JavaScript blob. - `File.t`: `File(File.t)`. A JavaScript file. - `RegExp.t`: `RegExp(RegExp.t)`. A JavaScript regexp instance. Again notice that the constructor names can be anything, what matters is what's in the payload. > **Under the hood**: Untagged variants uses a combination of JavaScript `typeof` and `instanceof` checks to discern between unboxed constructors at runtime. This means that we could add more things to the list above detailing what can be unboxed, if there are useful enough use cases. ### Pattern matching on unboxed variants Pattern matching works the same on unboxed variants as it does on regular variants. In fact, in the perspective of ReScript's type system there's no difference between untagged and tagged variants. You can do virtually the same things with both. That's the beauty of untagged variants - they're just variants to you as a developer. Here's an example of pattern matching on an unboxed nullable value that illustrates the above: ```rescript module Null = { @unboxed type t<'a> = Present('a) | @as(null) Null } type userAge = {ageNum: Null.t} type rec user = { name: string, age: Null.t, bestFriend: Null.t, } let getBestFriendsAge = user => switch user.bestFriend { | Present({age: Present({ageNum: Present(ageNum)})}) => Some(ageNum) | _ => None } ``` No difference to how you'd do with a regular variant. But, the runtime representation is different to a regular variant. > Notice how `@as` allows us to say that an untagged variant case should map to a specific underlying _primitive_. `Present` has a type variable, so it can hold any type. And since it's an unboxed type, only the payloads `'a` or `null` will be kept at runtime. That's where the magic comes from. ### Decoding and encoding JSON idiomatically With untagged variants, we have everything we need to define a native JSON type: ```rescript @unboxed type rec json = | @as(null) Null | Boolean(bool) | String(string) | Number(float) | Object(Dict.t) | Array(array) let myValidJsonValue = Array([String("Hi"), Number(123.)]) ``` Here's an example of how you could write your own JSON decoders easily using the above, leveraging pattern matching: ```rescript @unboxed type rec json = | @as(null) Null | Boolean(bool) | String(string) | Number(float) | Object(Dict.t) | Array(array) type rec user = { name: string, age: int, bestFriend: option, } let rec decodeUser = json => switch json { | Object(userDict) => switch ( userDict->Dict.get("name"), userDict->Dict.get("age"), userDict->Dict.get("bestFriend"), ) { | (Some(String(name)), Some(Number(age)), Some(maybeBestFriend)) => Some({ name, age: age->Float.toInt, bestFriend: maybeBestFriend->decodeUser, }) | _ => None } | _ => None } let decodeUsers = json => switch json { | Array(array) => array->Array.map(decodeUser)->Array.keepSome | _ => [] } ``` Encoding that same structure back into JSON is also easy: ```rescript let rec userToJson = user => Object( Dict.fromArray([ ("name", String(user.name)), ("age", Number(user.age->Int.toFloat)), ( "bestFriend", switch user.bestFriend { | None => Null | Some(friend) => userToJson(friend) }, ), ]), ) let usersToJson = users => Array(users->Array.map(userToJson)) ``` This can be extrapolated to many more cases. ### Advanced: Catch-all Constructors With untagged variants comes a rather interesting capability - catch-all cases are now possible to encode directly into a variant. Let's look at how it works. Imagine you're using a third party API that returns a list of available animals. You could of course model it as a regular `string`, but given that variants can be used as "typed strings", using a variant would give you much more benefit: ```rescript type animal = Dog | Cat | Bird type apiResponse = { animal: animal } let greetAnimal = (animal: animal) => switch animal { | Dog => "Wof" | Cat => "Meow" | Bird => "Kashiiin" } ```` ```javascript ```` This is all fine and good as long as the API returns `"Dog"`, `"Cat"` or `"Bird"` for `animal`. However, what if the API changes before you have a chance to deploy new code, and can now return `"Turtle"` as well? Your code would break down because the variant `animal` doesn't cover `"Turtle"`. So, we'll need to go back to `string`, loosing all of the goodies of using a variant, and then do manual conversion into the `animal` variant from `string`, right? Well, this used to be the case before, but not anymore! We can leverage untagged variants to bake in handling of unknown values into the variant itself. Let's update our type definition first: ```rescript @unboxed type animal = Dog | Cat | Bird | UnknownAnimal(string) ``` Notice we've added `@unboxed` and the constructor `UnknownAnimal(string)`. Remember how untagged variants work? You remove the constructors and just leave the payloads. This means that the variant above at runtime translates to this (made up) JavaScript type: ``` type animal = "Dog" | "Cat" | "Bird" | string ``` So, any string not mapping directly to one of the payloadless constructors will now map to the general `string` case. As soon as we've added this, the compiler complains that we now need to handle this additional case in our pattern match as well. Let's fix that: ```rescript @unboxed type animal = Dog | Cat | Bird | UnknownAnimal(string) type apiResponse = { animal: animal } let greetAnimal = (animal: animal) => switch animal { | Dog => "Wof" | Cat => "Meow" | Bird => "Kashiiin" | UnknownAnimal(otherAnimal) => `I don't know how to greet animal ${otherAnimal}` } ```` ```javascript function greetAnimal(animal) { if (!(animal === "Cat" || animal === "Dog" || animal === "Bird")) { return "I don't know how to greet animal " + animal; } switch (animal) { case "Dog" : return "Wof"; case "Cat" : return "Meow"; case "Bird" : return "Kashiiin"; } } ```` There! Now the external API can change as much as it wants, we'll be forced to write all code that interfaces with `animal` in a safe way that handles all possible cases. All of this baked into the variant definition itself, so no need for labor intensive manual conversion. This is useful in any scenario when you use something enum-style that's external and might change. Additionally, it's also useful when something external has a large number of possible values that are known, but where you only care about a subset of them. With a catch-all case you don't need to bind to all of them just because they can happen, you can safely just bind to the ones you care about and let the catch-all case handle the rest. ## Coercion In certain situations, variants can be coerced to other variants, or to and from primitives. Coercion is always zero cost. ### Coercing Variants to Other Variants You can coerce a variant to another variant if they're identical in runtime representation, and additionally if the variant you're coercing can be represented as the variant you're coercing to. Here's an example using [variant type spreads](#variant-type-spreads): ```rescript type a = One | Two | Three type b = | ...a | Four | Five let one: a = One let four: b = Four // This works because type `b` can always represent type `a` since all of type `a`'s constructors are spread into type `b` let oneAsTypeB = (one :> b) ``` ### Coercing Variants to Primitives Variants that are guaranteed to always be represented by a single primitive at runtime can be coerced to that primitive. It works with strings, the default runtime representation of payloadless constructors: ```rescript // Constructors without payloads are represented as `string` by default type a = One | Two | Three let one: a = One // All constructors are strings at runtime, so you can safely coerce it to a string let oneAsString = (one :> string) ``` If you were to configure all of your constructors to be represented as `int` or `float`, you could coerce to those too: ```rescript type asInt = | @as(1) One | @as(2) Two | @as(3) Three let oneInt: asInt = One let toInt = (oneInt :> int) ``` ### Advanced: Coercing `string` to Variant In certain situations it's possible to coerce a `string` to a variant. This is an advanced technique that you're unlikely to need much, but when you do it's really useful. You can coerce a `string` to a variant when: - Your variant is `@unboxed` - Your variant has a "catch-all" `string` case Let's look at an example: ```rescript @unboxed type myEnum = One | Two | Other(string) // Other("Other thing") let asMyEnum = ("Other thing" :> myEnum) // One let asMyEnum = ("One" :> myEnum) ``` This works because the variant is unboxed **and** has a catch-all case. So, if you throw a string at this variant that's not representable by the payloadless constructors, like `"One"` or `"Two"`, it'll _always_ end up in `Other(string)`, since that case can represent any `string`. ## Tips & Tricks **Be careful** not to confuse a constructor carrying 2 arguments with a constructor carrying a single tuple argument: ```res type accountWithTwoArguments = | Facebook(string, int) // 2 arguments type accountWithTupleArgument = | Instagram((string, int)) // 1 argument - happens to be a 2-tuple ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ### Variants Must Have Constructors If you come from an untyped language, you might be tempted to try `type myType = int | string`. This isn't possible in ReScript; you'd have to give each branch a constructor: `type myType = Int(int) | String(string)`. The former looks nice, but causes lots of trouble down the line. ### Interop with JavaScript _This section assumes knowledge about our JavaScript interop. Skip this if you haven't felt the itch to use variants for wrapping JS functions yet_. Quite a few JS libraries use functions that can accept many types of arguments. In these cases, it's very tempting to model them as variants. For example, suppose there's a `myLibrary.draw` JS function that takes in either a `number` or a `string`. You might be tempted to bind it like so: ```res // reserved for internal usage @module("myLibrary") external draw : 'a => unit = "draw" type animal = | MyFloat(float) | MyString(string) let betterDraw = (animal) => switch animal { | MyFloat(f) => draw(f) | MyString(s) => draw(s) } betterDraw(MyFloat(1.5)) ``` ```js import * as MyLibrary from "myLibrary"; function betterDraw(animal) { MyLibrary.draw(animal._0); } betterDraw({ TAG: "MyFloat", _0: 1.5, }); export { betterDraw }; ``` **Try not to do that**, as this generates extra noisy output. Instead, use the `@unboxed` attribute to guide ReScript to generate more efficient code: ```res // reserved for internal usage @module("myLibrary") external draw : 'a => unit = "draw" @unboxed type animal = | MyFloat(float) | MyString(string) let betterDraw = (animal) => switch animal { | MyFloat(f) => draw(f) | MyString(s) => draw(s) } betterDraw(MyFloat(1.5)) ``` ```js import * as MyLibrary from "myLibrary"; function betterDraw(animal) { MyLibrary.draw(animal); } MyLibrary.draw(1.5); export { betterDraw }; ``` Alternatively, define two `external`s that both compile to the same JS call: ```res @module("myLibrary") external drawFloat: float => unit = "draw" @module("myLibrary") external drawString: string => unit = "draw" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ReScript also provides [a few other ways](./bind-to-js-function.mdx#modeling-polymorphic-function) to do this. ### Variant Types Are Found By Field Name Please refer to this [record section](./record.mdx#tips--tricks). Variants are the same: a function can't accept an arbitrary constructor shared by two different variants. Again, such feature exists; it's called a polymorphic variant. We'll talk about this in the future =). ## Design Decisions Variants, in their many forms (polymorphic variant, open variant, GADT, etc.), are likely _the_ feature of a type system such as ReScript's. The aforementioned `option` variant, for example, obliterates the need for nullable types, a major source of bugs in other languages. Philosophically speaking, a problem is composed of many possible branches/conditions. Mishandling these conditions is the majority of what we call bugs. **A type system doesn't magically eliminate bugs; it points out the unhandled conditions and asks you to cover them**\*. The ability to model "this or that" correctly is crucial. For example, some folks wonder how the type system can safely eliminate badly formatted JSON data from propagating into their program. They don't, not by themselves! But if the parser returns the `option` type `None | Some(actualData)`, then you'd have to handle the `None` case explicitly in later call sites. That's all there is. Performance-wise, a variant can potentially tremendously speed up your program's logic. Here's a piece of JavaScript: ```js let data = 'dog' function logData(data) { if (data === 'dog') { ... } else if (data === 'cat') { ... } else if (data === 'bird') { ... } } logData(data) ``` There's a linear amount of branch checking here (`O(n)`). Compare this to using a ReScript variant: ```res type animal = Dog | Cat | Bird let data = Dog let logData = data => { switch data { | Dog => Console.log("Wof") | Cat => Console.log("Meow") | Bird => Console.log("Kashiiin") } } logData(data) ``` ```js function logData(data) { switch (data) { case "Dog": console.log("Wof"); return; case "Cat": console.log("Meow"); return; case "Bird": console.log("Kashiiin"); return; } } logData("Dog"); let data = "Dog"; export { data, logData }; ``` The compiler sees the variant, then 1. conceptually turns them into `type animal = "Dog" | "Cat" | "Bird"` 2. compiles `switch` to a constant-time jump table (`O(1)`). # Polymorphic Variant Polymorphic variants (or poly variant) are a cousin of [variant](./variant.mdx). With these differences: - They start with a `#` and the constructor name doesn't need to be capitalized. - They don't require an explicit type definition. The type is inferred from usage. - Values of different poly variant types can share the constructors they have in common (aka, poly variants are "structurally" typed, as opposed to ["nominally" typed](./variant.mdx#variant-types-are-found-by-field-name)). They're a convenient and useful alternative to regular variants, but should **not** be abused. See the drawbacks at the end of this page. ## Creation We provide 3 syntaxes for a poly variant's constructor: ```res let myColor = #red let myLabel = #"aria-hidden" let myNumber = #7 ``` ```js let myColor = "red"; let myLabel = "aria-hidden"; let myNumber = 7; export { myColor, myLabel, myNumber }; ``` **Take a look at the output**. Poly variants are _great_ for JavaScript interop. For example, you can use it to model JavaScript string and number enums like TypeScript, but without confusing their accidental usage with regular strings and numbers. `myColor` uses the common syntax. The second and third syntaxes are to support expressing strings and numbers more conveniently. We allow the second one because otherwise it'd be invalid syntax since symbols like `-` and others are usually reserved. ## Type Declaration Although **optional**, you can still pre-declare a poly variant type: ```res nocheck // Note the surrounding square brackets, and # for constructors type color = [#red | #green | #blue] ``` These types can also be inlined, unlike for regular variant: ```res let render = (myColor: [#red | #green | #blue]) => { switch myColor { | #blue => Console.log("Hello blue!") | #red | #green => Console.log("Hello other colors") } } ``` ```js function render(myColor) { if (myColor === "green" || myColor === "red") { console.log("Hello other colors"); } else { console.log("Hello blue!"); } } export { render }; ``` **Note**: because a poly variant value's type definition is **inferred** and not searched in the scope, the following snippet won't error: ```res type color = [#red | #green | #blue] let render = myColor => { switch myColor { | #blue => Console.log("Hello blue!") | #green => Console.log("Hello green!") // works! | #yellow => Console.log("Hello yellow!") } } ``` ```js function render(myColor) { if (myColor === "yellow") { console.log("Hello yellow!"); } else if (myColor === "green") { console.log("Hello green!"); } else { console.log("Hello blue!"); } } export { render }; ``` That `myColor` parameter's type is inferred to be `#red`, `#green` or `#yellow`, and is unrelated to the `color` type. If you intended `myColor` to be of type `color`, annotate it as `myColor: color` in any of the places. ## Constructor Arguments This is similar to a regular variant's [constructor arguments](./variant.mdx#constructor-arguments): ```res type account = [ | #Anonymous | #Instagram(string) | #Facebook(string, int) ] let me: account = #Instagram("Jenny") let him: account = #Facebook("Josh", 26) ``` ```js let me = { NAME: "Instagram", VAL: "Jenny", }; let him = { NAME: "Facebook", VAL: ["Josh", 26], }; export { me, him }; ``` ### Combine Types and Pattern Match You can use poly variant types within other poly variant types to create a sum of all constructors: ```res type red = [#Ruby | #Redwood | #Rust] type blue = [#Sapphire | #Neon | #Navy] // Contains all constructors of red and blue. // Also adds #Papayawhip type color = [red | blue | #Papayawhip] let myColor: color = #Ruby ``` ```js let myColor = "Ruby"; export { myColor }; ``` There's also some special [pattern matching](./pattern-matching-destructuring.mdx) syntax to match on constructors defined in a specific poly variant type: ```res nocheck // Continuing the previous example above... switch myColor { | #...blue => Console.log("This blue-ish") | #...red => Console.log("This red-ish") | other => Console.log2("Other color than red and blue: ", other) } ``` ```js var other = myColor; if (other === "Neon" || other === "Navy" || other === "Sapphire") { console.log("This is blue-ish"); } else if (other === "Rust" || other === "Ruby" || other === "Redwood") { console.log("This is red-ish"); } else { console.log("Other color than red and blue: ", other); } ``` This is a shorter version of: ```res nocheck switch myColor { | #Sapphire | #Neon | #Navy => Console.log("This is blue-ish") | #Ruby | #Redwood | #Rust => Console.log("This is red-ish") | other => Console.log2("Other color than red and blue: ", other) } ``` ## Structural Sharing Since poly variants value don't have a source of truth for their type, you can write such code: ```res type preferredColors = [#white | #blue] let myColor: preferredColors = #blue let displayColor = v => { switch v { | #red => "Hello red" | #green => "Hello green" | #white => "Hey white!" | #blue => "Hey blue!" } } Console.log(displayColor(myColor)) ``` ```js function displayColor(v) { if (v === "white") { return "Hey white!"; } else if (v === "red") { return "Hello red"; } else if (v === "green") { return "Hello green"; } else { return "Hey blue!"; } } console.log(displayColor("blue")); let myColor = "blue"; export { myColor, displayColor }; ``` With a regular variant, the line `displayColor(myColor)` would fail, since it'd complain that the type of `myColor` doesn't match the type of `v`. No problem with poly variant. ## JavaScript Output Poly variants are great for JavaScript interop! You can share their values to JS code, or model incoming JS values as poly variants. - `#red` and `#"I am red πŸ˜ƒ"` compile to JavaScipt `"red"` and `"I am red πŸ˜ƒ"`. - `#1` compiles to JavaScript `1`. - Poly variant constructor with 1 argument, like `Instagram("Jenny")` compile to a straightforward `{NAME: "Instagram", VAL: "Jenny"}`. 2 or more arguments like `#Facebook("Josh", 26)` compile to a similar object, but with `VAL` being an array of the arguments. ### Bind to Functions For example, let's assume we want to bind to `Intl.NumberFormat` and want to make sure that our users only pass valid locales, we could define an external binding like this: ```res type t @scope("Intl") @val external makeNumberFormat: ([#"de-DE" | #"en-GB" | #"en-US"]) => t = "NumberFormat" let intl = makeNumberFormat(#"de-DE") ``` ```js let intl = Intl.NumberFormat("de-DE"); export { intl }; ``` The JS output is identical to handwritten JS, but we also get to enjoy type errors if we accidentally write `makeNumberFormat(#"de-DR")`. More advanced usage examples for poly variant interop can be found in [Bind to JS Function](./bind-to-js-function.mdx#constrain-arguments-better). ### Bind to String Enums Let's assume we have a TypeScript module that expresses following enum export: ```js // direction.js enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT", } export const myDirection = Direction.Up ``` For this particular example, we can also inline poly variant type definitions to design the type for the imported `myDirection` value: ```res type direction = [ #UP | #DOWN | #LEFT | #RIGHT ] @module("./direction.js") external myDirection: direction = "myDirection" ``` ```js import * as DirectionJs from "./direction.js"; let myDirection = DirectionJs.myDirection; export { myDirection }; ``` Again: since we were using poly variants, the JS Output is practically zero-cost and doesn't add any extra code! ## Extra Constraints on Types The previous poly variant type annotations we've looked at are the regular "closed" kind. However, there's a way to express "I want at least these constructors" (lower bound) and "I want at most these constructors" (upper bound): ```res nocheck // Only #Red allowed. Closed. let basic: [#Red] = #Red // May contain #Red, or any other value. Open // here, foreground will actually be inferred as [> #Red | #Green] let foreground: [> #Red] = #Green // The value must be, at most, one of #Red or #Blue // Only #Red and #Blue are valid values let background: [< #Red | #Blue] = #Red ``` **Note:** We added this info for educational purposes. In most cases you will not want to use any of this stuff, since it makes your APIs pretty unreadable / hard to use. ### Closed `[` This is the simplest poly variant definition, and also the most practical one. Like a common variant type, this one defines an exact set of constructors. ```res nocheck type rgb = [ #Red | #Green | #Blue ] let color: rgb = #Green ``` In the example above, `color` will only allow one of the three constructors that are defined in the `rgb` type. This is usually the way how poly variants should be defined. In case you want to define a type that is extensible, you'll need to use the lower / upper bound syntax. ### Lower Bound `[>` A lower bound defines the minimum set of constructors a poly variant type is aware of. It is also considered an "open poly variant type", because it doesn't restrict any additional values. Here is an example on how to make a minimum set of `basicBlueTones` extensible for a new `color` type: ```res nocheck type basicBlueTone<'a> = [> #Blue | #DeepBlue | #LightBlue ] as 'a type color = basicBlueTone<[#Blue | #DeepBlue | #LightBlue | #Purple]> let color: color = #Purple // This will fail due to missing minimum constructors: type notWorking = basicBlueTone<[#Purple]> ``` Here, the compiler will enforce the user to define `#Blue | #DeepBlue | #LightBlue` as the minimum set of constructors when trying to extend `basicBlueTone<'a>`. **Note:** Since we want to define an extensible poly variant, we need to provide a type placeholder `<'a>`, and also add `as 'a` after the poly variant declaration, which essentially means: "Given type `'a` is constraint to the minimum set of constructors (`#Blue | #DeepBlue | #LightBlue`) defined in `basicBlueTone`". ### Upper Bound `[<` The upper bound works in the opposite way than a lower bound: the extending type may only use constructors that are stated in the upper bound constraint. Here another example, but with red colors: ```res nocheck type validRed<'a> = [< #Fire | #Crimson | #Ash] as 'a type myReds = validRed<[#Ash]> // This will fail due to unlisted constructor not defined by the lower bound type notWorking = validRed<[#Purple]> ``` ## Coercion You can convert a poly variant to a `string` or `int` at no cost: ```res type company = [#Apple | #Facebook] let theCompany: company = #Apple let message = "Hello " ++ (theCompany :> string) ``` ```js let message = "Hello " + "Apple"; let theCompany = "Apple"; export { theCompany, message }; ``` **Note**: for the coercion to work, the poly variant type needs to be closed; you'd need to annotate it, since otherwise, `theCompany` would be inferred as `[> #Apple]`. ## Tips & Tricks ### Variant vs Polymorphic Variant One might think that polymorphic variants are superior to regular [variants](./variant.mdx). As always, there are trade-offs: - Due to their "structural" nature, poly variant's type errors might be more confusing. If you accidentally write `#blur` instead of `#blue`, ReScript will still error but can't indicate the correct source as easily. Regular variants' source of truth is the type definition, so the error can't go wrong. - It's also harder to refactor poly variants. Consider this: ```res let myFruit = #Apple let mySecondFruit = #Apple let myCompany = #Apple ``` Refactoring the first one to `#Orange` doesn't mean we should refactor the third one. Therefore, the editor plugin can't touch the second one either. Regular variant doesn't have such problem, as these 2 values presumably come from different variant type definitions. - You might lose some nice pattern match checks from the compiler: ```res let myColor = #red switch myColor { | #red => Console.log("Hello red!") | #blue => Console.log("Hello blue!") } ``` Because there's no poly variant definition, it's hard to know whether the `#blue` case can be safely removed. In most scenarios, we'd recommend to use regular variants over polymorphic variants, especially when you are writing plain ReScript code. In case you want to write zero-cost interop bindings or generate clean JS output, poly variants are oftentimes a better option. # Null, Undefined and Option ReScript itself doesn't have the notion of `null` or `undefined`. This is a _great_ thing, as it wipes out an entire category of bugs. No more `undefined is not a function`, and `cannot access someAttribute of undefined`! However, the **concept** of a potentially nonexistent value is still useful, and safely exists in our language. We represent the existence and nonexistence of a value by wrapping it with the `option` type. Here's its definition from the standard library: ```res type option<'a> = None | Some('a) ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` It means "a value of type option is either None (representing nothing) or that actual value wrapped in a Some". **Note** how the `option` type is just a regular [variant](./variant.mdx). ## Example Here's a normal value: ```res let licenseNumber = 5 ``` ```js let licenseNumber = 5; export { licenseNumber }; ``` To represent the concept of "maybe null", you'd turn this into an `option` type by wrapping it. For the sake of a more illustrative example, we'll put a condition around it: ```res nocheck let licenseNumber = if personHasACar { Some(5) } else { None } ``` ```js var licenseNumber = personHasACar ? 5 : undefined; ``` Later on, when another piece of code receives such value, it'd be forced to handle both cases through [pattern matching](./pattern-matching-destructuring.mdx): ```res nocheck switch licenseNumber { | None => Console.log("The person doesn't have a car") | Some(number) => Console.log("The person's license number is " ++ Int.toString(number)) } ``` ```js var number = licenseNumber; if (number !== undefined) { console.log("The person's license number is " + number.toString()); } else { console.log("The person doesn't have a car"); } ``` By turning your ordinary number into an `option` type, and by forcing you to handle the `None` case, the language effectively removed the possibility for you to mishandle, or forget to handle, a conceptual `null` value! **A pure ReScript program doesn't have null errors**. ## Interoperate with JavaScript `undefined` and `null` The `option` type is common enough that we special-case it when compiling to JavaScript: ```res let x = Some(5) ``` ```js let x = 5; export { x }; ``` simply compiles down to `5`, and ```res let x = None ``` ```js let x; export { x }; ``` compiles to `undefined`! If you've got e.g. a string in JavaScript that you know might be `undefined`, type it as `option` and you're done! Likewise, you can send a `Some(5)` or `None` to the JS side and expect it to be interpreted correctly =) ### Caveat 1 Unfortunately, lots of times, your JavaScript value might be _both_ `null` or `undefined`. In that case, you unfortunately can't type such value as e.g. `option`, since our `option` type only checks for `undefined` and not `null` when dealing with a `None`. #### Solution: More Sophisticated `undefined` & `null` Interop To solve this, we provide access to more elaborate `null` and `undefined` helpers through the [`Nullable`](/docs/manual/api/stdlib/nullable) module. This somewhat works like an `option` type, but is different from it. #### Examples To create a JS `null`, use the value `Nullable.null`. To create a JS `undefined`, use `Nullable.undefined` (you can naturally use `None` too, but that's not the point here; the `Nullable.*` helpers wouldn't work with it). If you're receiving, for example, a JS string that can be `null` and `undefined`, type it as: ```res @module("MyConstant") external myId: Nullable.t = "myId" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` To create such a nullable string from our side (presumably to pass it to the JS side, for interop purpose), do: ```res @module("MyIdValidator") external validate: Nullable.t => bool = "validate" let personId: Nullable.t = Nullable.make("abc123") let result = validate(personId) ``` ```js import * as MyIdValidator from "MyIdValidator"; let personId = "abc123"; let result = MyIdValidator.validate(personId); export { personId, result }; ``` The `return` part "wraps" a string into a nullable string, to make the type system understand and track the fact that, as you pass this value around, it's not just a string, but a string that can be `null` or `undefined`. #### Convert to/from `option` `Nullable.fromOption` converts from a `option` to `Nullable.t`. `Nullable.toOption` does the opposite. # Array and List ## Array Arrays are the main ordered data structure in ReScript. They can be randomly accessed, dynamically resized, and updated. ```res let myArray = ["hello", "world", "how are you"] ``` ```js let myArray = ["hello", "world", "how are you"]; export { myArray }; ``` ReScript arrays' items must have the same type, i.e. homogeneous. ### Usage #### Access Accessing items in an array will return an `option` and can be done like so: ```res let myArray = ["hello", "world", "how are you"] let firstItem = myArray[0] // Some("hello") let tenthItem = myArray->Array.get(10) // None ``` ```js let myArray = ["hello", "world", "how are you"]; let firstItem = myArray[0]; let tenthItem = myArray[10]; export { myArray, firstItem, tenthItem }; ``` #### Update Items in an array can be updated by assigning a value to an index or using a function: ```res let myArray = ["hello", "world", "how are you"] myArray[0] = "hey" // now ["hey", "world", "how are you"] myArray->Array.push("?") // ["hey", "world", "how are you", "?"] myArray->Array.set(0, "bye") // ["bye", "world", "how are you", "?"] ``` ```js let myArray = ["hello", "world", "how are you"]; myArray[0] = "hey"; myArray.push("?"); myArray[0] = "bye"; export { myArray }; ``` ### Array spreads **Since 11.1** You can spread arrays of the same type into new arrays: ```res let y = [1, 2] let x = [4, 5, ...y] let x2 = [4, 5, ...y, 7, ...y] let x3 = [...y] ``` ```javascript import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js"; let y = [1, 2]; let x = Belt_Array.concatMany([[4, 5], y]); let x2 = Belt_Array.concatMany([[4, 5], y, [7], y]); let x3 = Belt_Array.concatMany([y]); export { y, x, x2, x3 }; ``` ## List ReScript provides a singly linked list too. Lists are: - immutable - fast at prepending items - fast at getting the head - slow at everything else ```res let myList = list{1, 2, 3} ``` ```js let myList = { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }; export { myList }; ``` Like arrays, lists' items need to be of the same type. ### Usage You'd use list for its resizability, its fast prepend (adding at the head), and its fast split, all of which are immutable and relatively efficient. Do **not** use list if you need to randomly access an item or insert at non-head position. Your code would end up obtuse and/or slow. For accessing deeper, see [destructuring](./pattern-matching-destructuring.mdx). #### Immutable Prepend Use the spread syntax: ```res prelude let myList = list{1, 2, 3} let anotherList = list{0, ...myList} ``` ```js var myList = { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: 0, }, }, }; var anotherList = { hd: 0, tl: myList, }; ``` `myList` didn't mutate. `anotherList` is now `list{0, 1, 2, 3}`. This is efficient (constant time, not linear). `anotherList`'s last 3 elements are shared with `myList`! **Note that `list{a, ...b, ...c}` was a syntax error** before compiler v10.1. In general, the pattern should be used with care as its performance and allocation overhead are linear (`O(n)`). #### Access `switch` (described in the [pattern matching section](./pattern-matching-destructuring.mdx)) is usually used to access list items: ```res let message = switch myList { | list{} => "This list is empty" | list{first, second, ...rest} => "The list two items and a potenial remainder of items" | list{a, ...rest} => "The head of the list is the string " ++ Int.toString(a) } ``` ```js let myList = { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }; let anotherList = { hd: 0, tl: myList, }; let message = myList !== 0 ? myList.tl !== 0 ? "The list two items and a potenial remainder of items" : "The head of the list is the string " + (1).toString() : "This list is empty"; export { myList, anotherList, message }; ``` # Function _Cheat sheet for the full function syntax at the end_. ReScript functions are declared with an arrow and return an expression. They compile to clean JavaScript functions. ```res prelude let greet = (name) => "Hello " ++ name ``` ```js function greet(name) { return "Hello " + name; } ``` This declares a function and assigns to it the name `greet`. When ReScript can evaluate a known pure function call ahead of time, it can emit the resulting data directly: ```res nocheck let message = greet("world!") // "Hello world!" ``` ```js function greet(name) { return "Hello " + name; } let message = "Hello world!"; export { greet, message }; ``` Multi-arguments functions have arguments separated by comma: If all the arguments are known up front, ReScript can precompute the result too: ```res nocheck let add = (x, y, z) => x + y + z let sum = add(1, 2, 3) // 6 ``` ```js function greet(name) { return "Hello " + name; } function add(x, y, z) { return (((x + y) | 0) + z) | 0; } let sum = 6; export { greet, add, sum }; ``` For longer functions, you'd surround the body with a block: ```res nocheck let greetMore = (name) => { let part1 = "Hello" part1 ++ " " ++ name } ``` ```js function greet(name) { return "Hello " + name; } function greetMore(name) { return "Hello " + name; } export { greet, greetMore }; ``` If your function has no argument, just write `let greetMore = () => {...}`. ## Labeled Arguments Multi-arguments functions, especially those whose arguments are of the same type, can be confusing to call. ```res nocheck let addCoordinates = (x, y) => { // use x and y here } // ... addCoordinates(5, 6) // which is x, which is y? ``` ```js function addCoordinates(x, y) { // use x and y here } addCoordinates(5, 6); ``` You can attach labels to an argument by prefixing the name with the `~` symbol: ```res nocheck let addCoordinates = (~x, ~y) => { // use x and y here } // ... addCoordinates(~x=5, ~y=6) ``` ```js function addCoordinates(x, y) { // use x and y here } addCoordinates(5, 6); ``` You can provide the arguments in **any order**: ```res nocheck addCoordinates(~y=6, ~x=5) ``` ```js addCoordinates(5, 6); ``` The `~x` part in the declaration means the function accepts an argument labeled `x` and can refer to it in the function body by the same name. You can also refer to the arguments inside the function body by a different name for conciseness: ```res nocheck let drawCircle = (~radius as r, ~color as c) => { setColor(c) startAt(r, r) // ... } drawCircle(~radius=10, ~color="red") ``` ```js function drawCircle(r, c) { setColor(c); return startAt(r, r); } drawCircle(10, "red"); ``` As a matter of fact, `(~radius)` is just a shorthand for `(~radius as radius)`. Here's the syntax for typing the arguments: ```res nocheck let drawCircle = (~radius as r: int, ~color as c: string) => { // code here } ``` ```js function drawCircle(r, c) { // code here } ``` ## Optional Labeled Arguments Labeled function arguments can be made optional during declaration. You can then omit them when calling the function. ```res nocheck // radius can be omitted let drawCircle = (~color, ~radius=?) => { setColor(color) switch radius { | None => startAt(1, 1) | Some(r_) => startAt(r_, r_) } } ``` ```js var Caml_option = require("./stdlib/caml_option.js"); function drawCircle(color, radius) { setColor(color); if (radius === undefined) { return startAt(1, 1); } var r_ = Caml_option.valFromOption(radius); return startAt(r_, r_); } ``` When given in this syntax, `radius` is **wrapped** in the standard library's `option` type, defaulting to `None`. If provided, it'll be wrapped with a `Some`. So `radius`'s type value is `None | Some(int)` here. Unlike [nullable](./null-undefined-option.mdx), optional fields don't ### Signatures and Type Annotations Functions with optional labeled arguments can be confusing when it comes to signature and type annotations. Indeed, the type of an optional labeled argument looks different depending on whether you're calling the function, or working inside the function body. Outside the function, a raw value is either passed in (`int`, for example), or left off entirely. Inside the function, the parameter is always there, but its value is an option (`option`). This means that the type signature is different, depending on whether you're writing out the function type, or the parameter type annotation. The first being a raw value, and the second being an option. If we get back to our previous example and both add a signature and type annotations to its argument, we get this: ```res nocheck let drawCircle: (~color: color, ~radius: int=?) => unit = (~color: color, ~radius: option=?) => { setColor(color) switch radius { | None => startAt(1, 1) | Some(r_) => startAt(r_, r_) } } ``` ```js function drawCircle(color, radius) { setColor(color); if (radius !== undefined) { return startAt(radius, radius); } else { return startAt(1, 1); } } ``` The first line is the function's signature, we would define it like that in an interface file (see [Signatures](./module.mdx#signatures)). The function's signature describes the types that the **outside world** interacts with, hence the type `int` for `radius` because it indeed expects an `int` when called. In the second line, we annotate the arguments to help us remember the types of the arguments when we use them **inside** the function's body, here indeed `radius` will be an `option` inside the function. So if you happen to struggle when writing the signature of a function with optional labeled arguments, try to remember this! ### Explicitly Passed Optional Sometimes, you might want to forward a value to a function without knowing whether the value is `None` or `Some(a)`. Naively, you'd do: ```res nocheck let result = switch payloadRadius { | None => drawCircle(~color) | Some(r) => drawCircle(~color, ~radius=r) } ``` ```js var r = payloadRadius; var result = r !== undefined ? drawCircle(color, Caml_option.valFromOption(r)) : drawCircle(color); ``` This quickly gets tedious. We provide a shortcut: ```res nocheck let result = drawCircle(~color, ~radius=?payloadRadius) ``` ```js var result = drawCircle(1, undefined); ``` This means "I understand `radius` is optional, and that when I pass it a value it needs to be an `int`, but I don't know whether the value I'm passing is `None` or `Some(val)`, so I'll pass you the whole `option` wrapper". ### Optional with Default Value Optional labeled arguments can also be provided a default value. In this case, they aren't wrapped in an `option` type. ```res nocheck let drawCircle = (~radius=1, ~color) => { setColor(color) startAt(radius, radius) } ``` ```js function drawCircle(radiusOpt, color) { var radius = radiusOpt !== undefined ? radiusOpt : 1; setColor(color); return startAt(radius, radius); } ``` ## Recursive Functions ReScript chooses the sane default of preventing a function to be called recursively within itself. To make a function recursive, add the `rec` keyword after the `let`: ```res nocheck let rec neverTerminate = () => neverTerminate() ``` ```js function greet(name) { return "Hello " + name; } function neverTerminate() { while (true) { continue; } } export { greet, neverTerminate }; ``` A simple recursive function may look like this: ```res nocheck // Recursively check every item on the list until one equals the `item` // argument. If a match is found, return `true`, otherwise return `false` let rec listHas = (list, item) => switch list { | list{} => false | list{a, ...rest} => a === item || listHas(rest, item) } ``` ```js function greet(name) { return "Hello " + name; } function listHas(_list, item) { while (true) { let list = _list; if (list === 0) { return false; } if (list.hd === item) { return true; } _list = list.tl; continue; } } export { greet, listHas }; ``` Recursively calling a function is bad for performance and the call stack. However, ReScript intelligently compiles [tail recursion](https://stackoverflow.com/questions/33923/what-is-tail-recursion) into a fast JavaScript loop. Try checking the JS output of the above code! ### Mutually Recursive Functions Mutually recursive functions start like a single recursive function using the `rec` keyword, and then are chained together with `and`: ```res nocheck let rec callSecond = () => callFirst() and callFirst = () => callSecond() ``` ```js function greet(name) { return "Hello " + name; } function callSecond() { while (true) { continue; } } function callFirst() { while (true) { continue; } } export { greet, callSecond, callFirst }; ``` ## Partial Application **Since 11.0** To partially apply a function, use the explicit `...` syntax. ```res nocheck let add = (a, b) => a + b let addFive = add(5, ...) ``` ```js function add(a, b) { return (a + b) | 0; } function addFive(extra) { return (5 + extra) | 0; } ``` ## Async/Await Just as in JS, an async function can be declared by adding `async` before the definition, and `await` can be used in the body of such functions. The output looks like idiomatic JS: ```res nocheck let getUserName = async (userId) => userId let greetUser = async (userId) => { let name = await getUserName(userId) "Hello " ++ name ++ "!" } ``` ```js function greet(name) { return "Hello " + name; } async function getUserName(userId) { return userId; } async function greetUser(userId) { let name = await getUserName(userId); return "Hello " + name + "!"; } export { greet, getUserName, greetUser }; ``` The return type of `getUser` is inferred to be `promise`. Similarly, `await getUserName(userId)` returns a `string` when the function returns `promise`. Using `await` outside of an `async` function (including in a non-async callback to an async function) is an error. ### Ergonomic error handling Error handling is done by simply using `try`/`catch`, or a switch with an `exception` case, just as in functions that are not async. Both JS exceptions and exceptions defined in ReScript can be caught. The compiler takes care of packaging JS exceptions into the builtin `JsError` exception: ```res nocheck exception SomeReScriptException let somethingThatMightThrow = async () => throw(SomeReScriptException) let someAsyncFn = async () => { switch await somethingThatMightThrow() { | data => Some(data) | exception JsExn(_) => None | exception SomeReScriptException => None } } ``` ```js import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function greet(name) { return "Hello " + name; } let SomeReScriptException = /* @__PURE__ */ Primitive_exceptions.create( "_tempFile.SomeReScriptException", ); async function somethingThatMightThrow() { throw { RE_EXN_ID: SomeReScriptException, Error: new Error(), }; } async function someAsyncFn() { let data; try { data = await somethingThatMightThrow(); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "JsExn") { return; } if (exn.RE_EXN_ID === SomeReScriptException) { return; } throw exn; } return Primitive_option.some(data); } export { greet, SomeReScriptException, somethingThatMightThrow, someAsyncFn }; ``` ## The ignore() Function Occasionally you may want to ignore the return value of a function. ReScript provides an `ignore()` function that discards the value of its argument and returns `()`: ```res nocheck mySideEffect()->Promise.catch(handleError)->ignore setTimeout(myFunc, 1000)->ignore ``` ```js $$Promise.$$catch(mySideEffect(), function (prim) { return handleError(prim); }); setTimeout(function (prim) { myFunc(); }, 1000); ``` ## Tips & Tricks Cheat sheet for the function syntaxes: ### Declaration ```res nocheck // anonymous function (x, y) => 1 // bind to a name let add = (x, y) => 1 // labeled let add = (~first as x, ~second as y) => x + y // with punning sugar let add = (~first, ~second) => first + second // labeled with default value let add = (~first as x=1, ~second as y=2) => x + y // with punning let add = (~first=1, ~second=2) => first + second // optional let add = (~first as x=?, ~second as y=?) => switch x {...} // with punning let add = (~first=?, ~second=?) => switch first {...} ``` #### With Type Annotation ```res nocheck // anonymous function (x: int, y: int): int => 1 // bind to a name let add = (x: int, y: int): int => 1 // labeled let add = (~first as x: int, ~second as y: int) : int => x + y // with punning sugar let add = (~first: int, ~second: int) : int => first + second // labeled with default value let add = (~first as x: int=1, ~second as y: int=2) : int => x + y // with punning sugar let add = (~first: int=1, ~second: int=2) : int => first + second // optional let add = (~first as x: option=?, ~second as y: option=?) : int => switch x {...} // with punning sugar // note that the caller would pass an `int`, not `option` // Inside the function, `first` and `second` are `option`. let add = (~first: option=?, ~second: option=?) : int => switch first {...} ``` ### Application ```res nocheck add(x, y) // labeled add(~first=1, ~second=2) // with punning sugar add(~first, ~second) // application with default value. Same as normal application add(~first=1, ~second=2) // explicit optional application add(~first=?Some(1), ~second=?Some(2)) // with punning add(~first?, ~second?) ``` #### With Type Annotation ```res nocheck // labeled add(~first=1: int, ~second=2: int) // with punning sugar add(~first: int, ~second: int) // application with default value. Same as normal application add(~first=1: int, ~second=2: int) // explicit optional application add(~first=?Some(1): option, ~second=?Some(2): option) // no punning sugar when you want to type annotate ``` ### Standalone Type Signature ```res nocheck // first arg type, second arg type, return type type add = (int, int) => int // labeled type add = (~first: int, ~second: int) => int // labeled type add = (~first: int=?, ~second: int=?, unit) => int ``` #### In Interface Files To annotate a function from the implementation file (`.res`) in your interface file (`.resi`): ```res sig let add: (int, int) => int ``` The type annotation syntax is the same as described in [With Type Annotation](#with-type-annotation) above. **Don't** confuse `let add: myType` with `type add = myType`. When used in `.resi` interface files, the former exports the binding `add` while annotating it as type `myType`. The latter exports the type `add`, whose value is the type `myType`. # If-Else & Loops ReScript supports `if`, `else`, ternary expression (`a ? b : c`), `for` and `while`. The `switch` pattern supports a default case. Read more about [pattern-matching & destructuring](./pattern-matching-destructuring.mdx). ## If-Else & Ternary ReScript's `if` is an expression; it evaluates to its body's content: ```res nocheck let message = if isMorning { "Good morning!" } else { "Hello!" } ``` ```js var message = isMorning ? "Good morning!" : "Hello!"; ``` **Note:** an `if-else` expression without the final `else` branch implicitly gives `()` (aka the `unit` type). So this: ```res nocheck if showMenu { displayMenu() } ``` ```js if (showMenu) { displayMenu(); } ``` is basically the same as: ```res nocheck if showMenu { displayMenu() } else { () } ``` ```js if (showMenu) { displayMenu(); } ``` Here's another way to look at it. This is clearly wrong: ```res nocheck let result = if showMenu { 1 + 2 } ``` It'll give a type error, saying basically that the implicit `else` branch has the type `unit` while the `if` branch has type `int`. Intuitively, this makes sense: what would `result`'s value be, if `showMenu` was `false`? We also have ternary sugar, but **we encourage you to prefer if-else when possible**. ```res nocheck let message = isMorning ? "Good morning!" : "Hello!" ``` ```js var message = isMorning ? "Good morning!" : "Hello!"; ``` `if-else` and ternary are often replaced by [pattern matching](./pattern-matching-destructuring.mdx), which handles a wide range of conditional logic more expressively. ## For Loops For loops iterate from a starting value up to (and including) the ending value. ```res nocheck for i in startValueInclusive to endValueInclusive { Console.log(i) } ``` ```js for (var i = startValueInclusive; i <= endValueInclusive; ++i) { console.log(i); } ``` ```res nocheck // prints: 1 2 3, one per line for x in 1 to 3 { Console.log(x) } ``` ```js for (let x = 1; x <= 3; ++x) { console.log(x); } ``` You can make the `for` loop count in the opposite direction by using `downto`. ```res nocheck for i in startValueInclusive downto endValueInclusive { Console.log(i) } ``` ```js for (var i = startValueInclusive; i >= endValueInclusive; --i) { console.log(i); } ``` ```res nocheck // prints: 3 2 1, one per line for x in 3 downto 1 { Console.log(x) } ``` ```js for (let x = 3; x >= 1; --x) { console.log(x); } ``` ## While Loops While loops execute its body code block while its condition is true. ```res nocheck while testCondition { // body here } ``` ```js while (testCondition) { // body here } ``` ### Tips & Tricks There's no loop-breaking `break` keyword (nor early `return` from functions, for that matter) in ReScript. However, we can break out of a while loop easily through using a [mutable binding](./mutation.mdx). ```res nocheck let break = ref(false) while !break.contents { if Math.random() > 0.3 { break := true } else { Console.log("Still running") } } ``` ```js let $$break = { contents: false, }; while (!$$break.contents) { if (Math.random() > 0.3) { $$break.contents = true; } else { console.log("Still running"); } } export { $$break }; ``` # Pipe ReScript provides a tiny but surprisingly useful operator `->`, called the "pipe", that allows you to "flip" your code inside-out. `a(b)` becomes `b->a`. It's a simple piece of syntax that doesn't have any runtime cost. Why would you use it? Imagine you have the following: ```res nocheck validateAge(getAge(parseData(person))) ``` ```js validateAge(getAge(parseData(person))); ``` This is slightly hard to read, since you need to read the code from the innermost part, to the outer parts. Use pipe to streamline it: ```res nocheck person ->parseData ->getAge ->validateAge ``` ```js validateAge(getAge(parseData(person))); ``` Basically, `parseData(person)` is transformed into `person->parseData`, and `getAge(person->parseData)` is transformed into `person->parseData->getAge`, etc. **This works when the function takes more than one argument too**. ```res nocheck a(one, two, three) ``` ```js a(one, two, three); ``` is the same as ```res nocheck one->a(two, three) ``` ```js a(one, two, three); ``` This also works with labeled arguments. Pipes are used to emulate object-oriented programming. For example, `myStudent.getName` in other languages like Java would be `myStudent->getName` in ReScript (equivalent to `getName(myStudent)`). This allows us to have the readability of OOP without the downside of dragging in a huge class system just to call a function on a piece of data. ## Tips & Tricks Do **not** abuse pipes; they're a means to an end. Inexperienced engineers sometimes shape a library's API to take advantage of the pipe. This is backwards. ## JS Method Chaining [bind to JS function](./bind-to-js-function.mdx) docs JavaScript's APIs are often attached to objects, and are often chainable, like so: ```js const result = [1, 2, 3].map((a) => a + 1).filter((a) => a % 2 === 0); asyncRequest().setWaitDuration(4000).send(); ``` Assuming we don't need the chaining behavior above, we'd bind to each case of this using [`@send`](../../syntax-lookup/decorator_send.mdx) from the aforementioned binding API page: ```res prelude type request @val external asyncRequest: unit => request = "asyncRequest" @send external setWaitDuration: (request, int) => request = "setWaitDuration" @send external send: request => unit = "send" ``` ```js // Empty output ``` You'd use them like this: ```res nocheck let result = Array.filter( Array.map([1, 2, 3], a => a + 1), a => mod(a, 2) == 0 ) send(setWaitDuration(asyncRequest(), 4000)) ``` ```js let result = [1, 2, 3].map((a) => (a + 1) | 0).filter((a) => a % 2 === 0); asyncRequest().setWaitDuration(4000).send(); export { result }; ``` This looks much worse than the JS counterpart! Clean it up visually with pipe: ```res nocheck let result = [1, 2, 3] ->Array.map(a => a + 1) ->Array.filter(a => mod(a, 2) == 0) asyncRequest()->setWaitDuration(4000)->send ``` ```js let result = [1, 2, 3].map((a) => (a + 1) | 0).filter((a) => a % 2 === 0); asyncRequest().setWaitDuration(4000).send(); export { result }; ``` ## Pipe Into Variants You can pipe into a variant's constructor as if it was a function: ```res nocheck let result = name->preprocess->Some ``` ```js var result = preprocess(name); ``` We turn this into: ```res nocheck let result = Some(preprocess(name)) ``` ```js var result = preprocess(name); ``` **Note** that using a variant constructor as a function wouldn't work anywhere else beside here. ## Pipe Placeholders A placeholder is written as an underscore and it tells ReScript that you want to fill in an argument of a function later. These two have equivalent meaning: ```res nocheck let addTo7 = (x) => add3(3, x, 4) let addTo7 = add3(3, _, 4) ``` Sometimes you don't want to pipe the value you have into the first position. In these cases you can mark a placeholder value to show which argument you would like to pipe into. Let's say you have a function `namePerson`, which takes a `person` then a `name` argument. If you are transforming a person then pipe will work as-is: ```res nocheck makePerson(~age=47) ->namePerson("Jane") ``` ```js namePerson(makePerson(47), "Jane"); ``` If you have a name that you want to apply to a person object, you can use a placeholder: ```res nocheck getName(input) ->namePerson(personDetails, _) ``` ```js var __x = getName(input); namePerson(personDetails, __x); ``` This allows you to pipe into any positional argument. It also works for named arguments: ```res nocheck getName(input) ->namePerson(~person=personDetails, ~name=_) ``` ```js var __x = getName(input); namePerson(personDetails, __x); ``` # Pattern Matching / Destructuring One of ReScript's **best** features is our pattern matching. Pattern matching combines 3 brilliant features into one: - Destructuring. - `switch` based on shape of data. - Exhaustiveness check. We'll dive into each aspect below. ## Destructuring Even JavaScript has destructuring, which is "opening up" a data structure to extract the parts we want and assign variable names to them: ```res let coordinates = (10, 20, 30) let (x, _, _) = coordinates Console.log(x) // 10 ``` ```js console.log(10); let coordinates = [10, 20, 30]; let x = 10; export { coordinates, x }; ``` Destructuring works with most built-in data structures: ```res // Record type student = {name: string, age: int} let student1 = {name: "John", age: 10} let {name} = student1 // "John" assigned to `name` // Variant type result = | Success(string) let myResult = Success("You did it!") let Success(message) = myResult // "You did it!" assigned to `message` ``` ```js let student1 = { name: "John", age: 10, }; let name = "John"; let myResult = { TAG: "Success", _0: "You did it!", }; let message = "You did it!"; export { student1, name, myResult, message }; ``` You can also use destructuring anywhere you'd usually put a binding: ```res type result = | Success(string) let displayMessage = (Success(m)) => { // we've directly extracted the success message // string by destructuring the parameter Console.log(m) } displayMessage(Success("You did it!")) ``` ```js function displayMessage(m) { console.log(m._0); } displayMessage({ TAG: "Success", _0: "You did it!", }); export { displayMessage }; ``` For a record, you can rename the field while destructuring: ```res nocheck let {name: n} = student1 // "John" assigned to `n` ``` ```js var n = "John"; ``` You _can_ in theory destructure array and list at the top level too: ```res nocheck let myArray = [1, 2, 3] let [item1, item2, _] = myArray // 1 assigned to `item1`, 2 assigned to `item2`, 3rd item ignored let myList = list{1, 2, 3} let list{head, ...tail} = myList // 1 assigned to `head`, `list{2, 3}` assigned to tail ``` But the array example is **highly disrecommended** (use tuple instead) and the list example will error on you. They're only there for completeness' sake. As you'll see below, the proper way of using destructuring array and list is using `switch`. ## `switch` Based on Shape of Data While the destructuring aspect of pattern matching is nice, it doesn't really change the way you think about structuring your code. One paradigm-changing way of thinking about your code is to execute some code based on the shape of the data. Consider a variant: ```res prelude type payload = | BadResult(int) | GoodResult(string) | NoResult ``` ```js // Empty output ``` We'd like to handle each of the 3 cases differently. For example, print a success message if the value is `GoodResult(...)`, do something else when the value is `NoResult`, etc. In other languages, you'd end up with a series of if-elses that are hard to read and error-prone. In ReScript, you can instead use the supercharged `switch` pattern matching facility to destructure the value while calling the right code based on what you destructured: ```res let data = GoodResult("Product shipped!") switch data { | GoodResult(theMessage) => Console.log("Success! " ++ theMessage) | BadResult(errorCode) => Console.log("Something's wrong. The error code is: " ++ Int.toString(errorCode)) | NoResult => Console.log("Bah.") } ``` ```js let data = { TAG: "GoodResult", _0: "Product shipped!", }; if (typeof data !== "object") { console.log("Bah."); } else if (data.TAG === "BadResult") { console.log( "Something's wrong. The error code is: " + "Product shipped!".toString(), ); } else { console.log("Success! Product shipped!"); } export { data }; ``` In this case, `message` will have the value `"Success! Product shipped!"`. Suddenly, your if-elses that messily checks some structure of the value got turned into a clean, compiler-verified, linear list of code to execute based on exactly the shape of the value. ### Complex Examples Here's a real-world scenario that'd be a headache to code in other languages. Given this data structure: ```res prelude type status = Vacations(int) | Sabbatical(int) | Sick | Present type reportCard = {passing: bool, gpa: float} type student = {name: string, status: status, reportCard: reportCard} type person = | Teacher({name: string, age: int}) | Student(student) ``` ```js // Empty output ``` Imagine this requirement: - Informally greet a person who's a teacher and if his name is Mary or Joe. - Greet other teachers formally. - If the person's a student, congratulate him/her score if they passed the semester. - If the student has a gpa of 0 and is on vacations or sabbatical, display a different message. - A catch-all message for a student. ReScript can do this easily! ```res prelude let person1 = Teacher({name: "Jane", age: 35}) let message = switch person1 { | Teacher({name: "Mary" | "Joe"}) => `Hey, still going to the party on Saturday?` | Teacher({name}) => // this is matched only if `name` isn't "Mary" or "Joe" `Hello ${name}.` | Student({name, reportCard: {passing: true, gpa}}) => `Congrats ${name}, nice GPA of ${Float.toString(gpa)} you got there!` | Student({ reportCard: {gpa: 0.0}, status: Vacations(daysLeft) | Sabbatical(daysLeft) }) => `Come back in ${Int.toString(daysLeft)} days!` | Student({status: Sick}) => `How are you feeling?` | Student({name}) => `Good luck next semester ${name}!` } ``` ```js var person1 = { TAG: "Teacher", name: "Jane", age: 35, }; var message; if (person1.TAG === "Teacher") { message = "Hello Jane."; } else { var match = "Jane"; var match$1 = match.status; var name = match.name; var match$2 = match.reportCard; if (match$2.passing) { message = "Congrats " + name + ", nice GPA of " + match$2.gpa.toString() + " you got there!"; } else { var exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? "How are you feeling?" : "Good luck next semester " + name + "!"; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? "Good luck next semester " + name + "!" : "Come back in " + match$1._0.toString() + " days!"; } } } ``` **Note** how we've: - drilled deep down into the value concisely - using a **nested pattern check** `"Mary" | "Joe"` and `Vacations | Sabbatical` - while extracting the `daysLeft` number from the latter case - and assigned the greeting to the binding `message`. Here's another example of pattern matching, this time on an inline tuple. ```res nocheck type animal = Dog | Cat | Bird let categoryId = switch (isBig, myAnimal) { | (true, Dog) => 1 | (true, Cat) => 2 | (true, Bird) => 3 | (false, Dog | Cat) => 4 | (false, Bird) => 5 } ``` ```js var categoryId = isBig ? (myAnimal + 1) | 0 : myAnimal >= 2 ? 5 : 4; ``` **Note** how pattern matching on a tuple is equivalent to a 2D table: | isBig \ myAnimal | Dog | Cat | Bird | | ---------------- | --- | --- | ---- | | true | 1 | 2 | 3 | | false | 4 | 4 | 5 | ### Fall-Through Patterns The nested pattern check, demonstrated in the earlier `person` example, also works at the top level of a `switch`: ```res prelude let myStatus = Vacations(10) switch myStatus { | Vacations(days) | Sabbatical(days) => Console.log(`Come back in ${Int.toString(days)} days!`) | Sick | Present => Console.log("Hey! How are you?") } ``` ```js var myStatus = { TAG: /* Vacations */ 0, _0: 10, }; if (typeof myStatus === "number") { console.log("Hey! How are you?"); } else { console.log("Come back in " + (10).toString() + " days!"); } ``` Having multiple cases fall into the same handling can clean up certain types of logic. ### Ignore Part of a Value If you have a value like `Teacher(payload)` where you just want to pattern match on the `Teacher` part and ignore the `payload` completely, you can use the `_` wildcard like this: ```res switch person1 { | Teacher(_) => Console.log("Hi teacher") | Student(_) => Console.log("Hey student") } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } if (person1.TAG === "Teacher") { console.log("Hi teacher"); } else { console.log("Hey student"); } export { person1, message, myStatus }; ``` `_` also works at the top level of the `switch`, serving as a catch-all condition: ```res switch myStatus { | Vacations(_) => Console.log("Have fun!") | _ => Console.log("Ok.") } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } if (typeof myStatus !== "object" || myStatus.TAG !== "Vacations") { console.log("Ok."); } else { console.log("Have fun!"); } export { person1, message, myStatus }; ``` **Do not** abuse a top-level catch-all condition. Instead, prefer writing out all the cases: ```res switch myStatus { | Vacations(_) => Console.log("Have fun!") | Sabbatical(_) | Sick | Present => Console.log("Ok.") } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } if (typeof myStatus !== "object" || myStatus.TAG !== "Vacations") { console.log("Ok."); } else { console.log("Have fun!"); } export { person1, message, myStatus }; ``` Slightly more verbose, but a one-time writing effort. This helps when you add a new variant case e.g. `Quarantined` to the `status` type and need to update the places that pattern match on it. A top-level wildcard here would have accidentally and silently continued working, potentially causing bugs. ### If Clause Sometime, you want to check more than the shape of a value. You want to also run some arbitrary check on it. You might be tempted to write this: ```res switch person1 { | Teacher(_) => () // do nothing | Student({reportCard: {gpa}}) => if gpa < 0.5 { Console.log("What's happening") } else { Console.log("Heyo") } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } if (person1.TAG !== "Teacher") { if ("Jane".reportCard.gpa < 0.5) { console.log("What's happening"); } else { console.log("Heyo"); } } export { person1, message, myStatus }; ``` `switch` patterns support a shortcut for the arbitrary `if` check, to keep your pattern linear-looking: ```res switch person1 { | Teacher(_) => () // do nothing | Student({reportCard: {gpa}}) if gpa < 0.5 => Console.log("What's happening") | Student(_) => // fall-through, catch-all case Console.log("Heyo") } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } if (person1.TAG !== "Teacher") { if ("Jane".reportCard.gpa < 0.5) { console.log("What's happening"); } else { console.log("Heyo"); } } export { person1, message, myStatus }; ``` ### Match on subtype variants You can refine a variant A to variant B using the [variant type spread syntax](./variant.mdx#variant-type-spreads) in pattern matching. This is possible if variant B [is a subtype of](./variant.mdx#coercion) variant A. Let's look at an example: ```res type pets = Cat | Dog type fish = Cod | Salmon type animals = | ...pets | ...fish let greetPet = (pet: pets) => { switch pet { | Cat => Console.log("Hello kitty!") | Dog => Console.log("Woof woof doggie!") } } let greetFish = (fish: fish) => { switch fish { | Cod => Console.log("Blub blub..") | Salmon => Console.log("Blub blub blub blub..") } } let greetAnimal = (animal: animals) => { switch animal { | ...pets as pet => greetPet(pet) | ...fish as fish => greetFish(fish) } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } function greetPet(pet) { if (pet === "Cat") { console.log("Hello kitty!"); return; } console.log("Woof woof doggie!"); } function greetFish(fish) { if (fish === "Cod") { console.log("Blub blub.."); return; } console.log("Blub blub blub blub.."); } function greetAnimal(animal) { switch (animal) { case "Cat": case "Dog": return greetPet(animal); case "Cod": case "Salmon": return greetFish(animal); } } export { person1, message, myStatus, greetPet, greetFish, greetAnimal }; ``` Let's break down what we did: - Defined two different variants for pets and for fish - Wrote a dedicated function per animal type to greet that particular type of animal - Combined `pets` and `fish` into a main variant for `animals` - Wrote a function that can greet any animal by _spreading_ each sub variant on its own branch, aliasing that spread to a variable, and passing that variable to the dedicated greet function for that specific type Notice how we're able to match on parts of the main variant, as long as the variants are compatible. The example above aliases the variant type spread to a variable so we can use it in our branch. But, you can just as easily match without aliasing if you don't care about the value: ```res nocheck let isPet = (animal: animals) => { switch animal { | ...pets => Console.log("A pet!") | _ => Console.log("Not a pet...") } } ``` ```js function isPet(animal) { switch (animal) { case "Cat": case "Dog": console.log("A pet!"); return; case "Cod": case "Salmon": console.log("Not a pet..."); return; } } ``` Similarily, if you want to get advanced, you can even pull out a single variant constructor. This works with and without aliases. Example: ```res type dog = Dog type pets = Cat | ...dog type fish = Cod | Salmon type animals = | ...pets | ...fish let isPet = (animal: animals) => { switch animal { | ...dog => Console.log("A dog!") | _ => Console.log("Not a dog...") } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } function isPet(animal) { if (animal === "Dog") { console.log("A dog!"); return; } console.log("Not a dog..."); } export { person1, message, myStatus, isPet }; ``` And, thanks to the rules of subtyping, the `Dog` constructor wouldn't _really_ need to be spread inside of the `pets` variant for this to work: ```res type pets = Cat | Dog type fish = Cod | Salmon type animals = | ...pets | ...fish // Notice `dog` isn't spread into the `pets` variant, // but this still work due to subtyping. type dog = Dog let isPet = (animal: animals) => { switch animal { | ...dog => Console.log("A dog!") | _ => Console.log("Not a dog...") } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } function isPet(animal) { if (animal === "Dog") { console.log("A dog!"); return; } console.log("Not a dog..."); } export { person1, message, myStatus, isPet }; ``` ### Match on Exceptions If the function throws an exception (covered later), you can also match on _that_, in addition to the function's normally returned values. ```res nocheck switch List.find(i => i === theItem, myItems) { | item => Console.log(item) | exception Not_found => Console.log("No such item found!") } ``` ```js var exit = 0; var item; try { item = List.find(function (i) { return i === theItem; }, myItems); exit = 1; } catch (raw_exn) { var exn = Caml_js_exceptions.internalToOCamlException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn; } } if (exit === 1) { console.log(item); } ``` ### Match on Array ```res let students = ["Jane", "Harvey", "Patrick"] switch students { | [] => Console.log("There are no students") | [student1] => Console.log("There's a single student here: " ++ student1) | manyStudents => // display the array of names Console.log2("The students are: ", manyStudents) } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let students = ["Jane", "Harvey", "Patrick"]; let len = students.length; if (len !== 1) { if (len !== 0) { console.log("The students are: ", students); } else { console.log("There are no students"); } } else { let student1 = students[0]; console.log("There's a single student here: " + student1); } export { person1, message, myStatus, students }; ``` ### Match on List Pattern matching on list is similar to array, but with the extra feature of extracting the tail of a list (all elements except the first one): ```res let rec printStudents = (students) => { switch students { | list{} => () // done | list{student} => Console.log("Last student: " ++ student) | list{student1, ...otherStudents} => Console.log(student1) printStudents(otherStudents) } } printStudents(list{"Jane", "Harvey", "Patrick"}) ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } function printStudents(_students) { while (true) { let students = _students; if (students === 0) { return; } let otherStudents = students.tl; let student = students.hd; if (otherStudents !== 0) { console.log(student); _students = otherStudents; continue; } console.log("Last student: " + student); return; } } printStudents({ hd: "Jane", tl: { hd: "Harvey", tl: { hd: "Patrick", tl: /* [] */ 0, }, }, }); export { person1, message, myStatus, printStudents }; ``` ### Match on Dictionaries You can pattern match on dictionaries just like you can on other ReScript data structures. When pattern matching on a dictionary it's assumed by default that you're expecting the keys you match on to exist in the dictionary. Example: ```res prelude let d = dict{"A": 5, "B": 6} // We're expecting the `B` key to exist below, and `b` will be `int` in the match branch let b = switch d { | dict{"B": b} => Some(b) | _ => None } ``` ```js let d = { A: 5, B: 6, }; let b = d.B; let b$1 = b !== undefined ? b : undefined; ``` However, there are situations where you want to pull out the value of a key as an option. You can do that using the `?` optional syntax in the pattern match: ```res prelude let d = dict{"A": 5, "B": 6} // We're pulling out `B` regardless of if it has a value or not, and therefore get `b` as `option` let b = switch d { | dict{"B": ?b} => b } ``` ```js let d = { A: 5, B: 6, }; let b = d.B; ``` Notice how in the first case, when not using `?`, we had to supply a catch-all case `_`. That's because the pattern match _expects_ `B` to exist in the first case, for the pattern to match. If `B` doesn't exist, the match falls through to the next branch, and therefore we need to catch it to be exhaustive in our matching. However, in the second case, we don't need a catch-all case. That's because the first branch will _always_ match the dictionary - either `B` exists or it doesn't, but it doesn't matter because we're pulling it out as an optional value. ### Small Pitfall **Note**: you can only pass literals (i.e. concrete values) as a pattern, not let-binding names or other things. The following doesn't work as expected: ```res let coordinates = (10, 20, 30) let centerY = 20 switch coordinates { | (x, _centerY, _) => Console.log(x) } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; console.log(10); let b = d.B; let coordinates = [10, 20, 30]; let centerY = 20; export { person1, message, myStatus, d, b, coordinates, centerY }; ``` A first time ReScript user might accidentally write that code, assuming that it's matching on `coordinates` when the second value is of the same value as `centerY`. In reality, this is interpreted as matching on coordinates and assigning the second value of the tuple to the name `centerY`, which isn't what's intended. ## Exhaustiveness Check As if the above features aren't enough, ReScript also provides arguably the most important pattern matching feature: **compile-time check of missing patterns**. Let's revisit one of the above examples: ```res nocheck let message = switch person1 { | Teacher({name: "Mary" | "Joe"}) => `Hey, still going to the party on Saturday?` | Student({name, reportCard: {passing: true, gpa}}) => `Congrats ${name}, nice GPA of ${Float.toString(gpa)} you got there!` | Student({ reportCard: {gpa: 0.0}, status: Vacations(daysLeft) | Sabbatical(daysLeft) }) => `Come back in ${Int.toString(daysLeft)} days!` | Student({status: Sick}) => `How are you feeling?` | Student({name}) => `Good luck next semester ${name}!` } ``` ```js if (person1.TAG) { var match$1 = person1.status; var name = person1.name; var match$2 = person1.reportCard; if (match$2.passing) { "Congrats " + name + ", nice GPA of " + match$2.gpa.toString() + " you got there!"; } else if (typeof match$1 === "number") { if (match$1 !== 0) { "Good luck next semester " + name + "!"; } else { ("How are you feeling?"); } } else if (person1.reportCard.gpa !== 0.0) { "Good luck next semester " + name + "!"; } else { "Come back in " + match$1._0.toString() + " days!"; } } else { switch (person1.name) { case "Joe": case "Mary": break; default: throw { RE_EXN_ID: "Match_failure", _1: ["playground.res", 13, 0], Error: new Error(), }; } } ``` Did you see what we removed? This time, we've omitted the handling of the case where `person1` is `Teacher({name})` when `name` isn't Mary or Joe. Failing to handle every scenario of a value likely constitutes the majority of program bugs out there. This happens very often when you refactor a piece of code someone else wrote. Fortunately for ReScript, the compiler will tell you so: ``` Warning 8: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: Some({name: ""}) ``` **BAM**! You've just erased an entire category of important bugs before you even ran the code. In fact, this is how most of nullable values is handled: ```res let myNullableValue = Some(5) switch myNullableValue { | Some(_v) => Console.log("value is present") | None => Console.log("value is absent") } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; console.log("value is present"); let b = d.B; let myNullableValue = 5; export { person1, message, myStatus, d, b, myNullableValue }; ``` If you don't handle the `None` case, the compiler warns. No more `undefined` bugs in your code! ## Conclusion & Tips & Tricks Hopefully you can see how pattern matching is a game changer for writing correct code, through the concise destructuring syntax, the proper conditions handling of `switch`, and the static exhaustiveness check. Below is some advice: Avoid using the wildcard `_` unnecessarily. Using the wildcard `_` will bypass the compiler's exhaustiveness check. Consequently, the compiler will not be able to notify you of probable errors when you add a new case to a variant. Try only using `_` against infinite possibilities, e.g. string, int, etc. Use the `if` clause sparingly. **Flatten your pattern-match whenever you can**. This is a real bug remover. Here's a series of examples, from worst to best: ```res let optionBoolToBool = opt => { if opt == None { false } else if opt === Some(true) { true } else { false } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; function optionBoolToBool(opt) { if (opt === undefined) { return false; } else { return opt === true; } } let b = d.B; export { person1, message, myStatus, d, b, optionBoolToBool }; ``` Now that's just silly =). Let's turn it into pattern-matching: ```res let optionBoolToBool = opt => { switch opt { | None => false | Some(a) => a ? true : false } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; function optionBoolToBool(opt) { if (opt !== undefined) { return opt; } else { return false; } } let b = d.B; export { person1, message, myStatus, d, b, optionBoolToBool }; ``` Slightly better, but still nested. Pattern-matching allows you to do this: ```res let optionBoolToBool = opt => { switch opt { | None => false | Some(true) => true | Some(false) => false } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; function optionBoolToBool(opt) { if (opt !== undefined) { return opt; } else { return false; } } let b = d.B; export { person1, message, myStatus, d, b, optionBoolToBool }; ``` Much more linear-looking! Now, you might be tempted to do this: ```res let optionBoolToBool = opt => { switch opt { | Some(true) => true | _ => false } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; function optionBoolToBool(opt) { if (opt !== undefined) { return opt; } else { return false; } } let b = d.B; export { person1, message, myStatus, d, b, optionBoolToBool }; ``` Which is much more concise, but kills the exhaustiveness check mentioned above; refrain from using that. This is the best: ```res let optionBoolToBool = opt => { switch opt { | Some(trueOrFalse) => trueOrFalse | None => false } } ``` ```js let person1 = { TAG: "Teacher", name: "Jane", age: 35, }; let message; if (person1.TAG === "Teacher") { message = `Hello ` + "Jane" + `.`; } else { let match = "Jane"; let match$1 = match.status; let name = match.name; let match$2 = match.reportCard; if (match$2.passing) { message = `Congrats ` + name + `, nice GPA of ` + match$2.gpa.toString() + ` you got there!`; } else { let exit = 0; if (typeof match$1 !== "object") { message = match$1 === "Sick" ? `How are you feeling?` : `Good luck next semester ` + name + `!`; } else { exit = 1; } if (exit === 1) { message = match.reportCard.gpa !== 0.0 ? `Good luck next semester ` + name + `!` : `Come back in ` + match$1._0.toString() + ` days!`; } } } let myStatus = { TAG: "Vacations", _0: 10, }; let exit$1 = 0; if (typeof myStatus !== "object") { console.log("Hey! How are you?"); } else { exit$1 = 1; } if (exit$1 === 1) { console.log(`Come back in ` + (10).toString() + ` days!`); } let d = { A: 5, B: 6, }; function optionBoolToBool(opt) { if (opt !== undefined) { return opt; } else { return false; } } let b = d.B; export { person1, message, myStatus, d, b, optionBoolToBool }; ``` Pretty darn hard to make a mistake in this code at this point! Whenever you'd like to use an if-else with many branches, prefer pattern matching instead. It's more concise and [performant](./variant.mdx#design-decisions) too. # Mutation ReScript has great traditional imperative & mutative programming capabilities. You should use these features sparingly, but sometimes they allow your code to be more performant and written in a more familiar pattern. ## Mutate Let-binding Let-bindings are immutable, but you can wrap it with a `ref`, exposed as a record with a single mutable field in the standard library: ```res prelude let myValue = ref(5) ``` ```js var myValue = { contents: 5, }; ``` ## Usage You can get the actual value of a `ref` box through accessing its `contents` field: ```res let five = myValue.contents // 5 ``` ```js let myValue = { contents: 5, }; let five = myValue.contents; export { myValue, five }; ``` Assign a new value to `myValue` like so: ```res myValue.contents = 6 ``` ```js let myValue = { contents: 5, }; myValue.contents = 6; export { myValue }; ``` We provide a syntax sugar for this: ```res myValue := 6 ``` ```js let myValue = { contents: 5, }; myValue.contents = 6; export { myValue }; ``` Note that the previous binding `five` stays `5`, since it got the underlying item on the `ref` box, not the `ref` itself. **Note**: you might see in the JS output tabs above that `ref` allocates an object. Worry not; local, non-exported `ref`s allocations are optimized away. ## Tip & Tricks Before reaching for `ref`, know that you can achieve lightweight, local "mutations" through [overriding let bindings](./let-binding.mdx#binding-shadowing). # JSX Would you like some HTML syntax in your ReScript? If not, quickly skip over this section and pretend you didn't see anything! ReScript supports the JSX syntax, with some slight differences compared to the one in [ReactJS](https://facebook.github.io/react/docs/introducing-jsx.html). ReScript JSX isn't tied to ReactJS; they translate to normal function calls: **Note** for [ReScriptReact](../react/introduction.mdx) readers: this isn't what ReScriptReact turns JSX into, in the end. See Usage section for more info. ## Capitalized ```res nocheck ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx(MyComponent, { name: "ReScript", }); ``` becomes ```res nocheck React.jsx(MyComponent.make, {name: {"ReScript"}}) ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx(MyComponent, { name: "ReScript", }); ``` ## Uncapitalized ```res nocheck
child1 child2
``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs("div", { children: [child1, child2], onClick: handler, }); ```
becomes ```res nocheck ReactDOM.jsxs("div", {onClick: {handler}, children: React.array([child1, child2])}) ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs("div", { children: [child1, child2], onClick: handler, }); ``` ## Fragment ```res nocheck <> child1 child2 ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs(JsxRuntime.Fragment, { children: [child1, child2], }); ``` becomes ```res nocheck React.jsxs(React.jsxFragment, {children: React.array([child1, child2])}) ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs(JsxRuntime.Fragment, { children: [child1, child2], }); ``` ### Children ```res nocheck child1 child2 ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs(MyComponent, { children: [child1, child2], }); ``` This is the syntax for passing a list of two items, `child1` and `child2`, to the children position. It transforms to a list containing `child1` and `child2`: ```res nocheck React.jsxs(MyComponent.make, {children: React.array([child1, child2])}) ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsxs(MyComponent, { children: [child1, child2], }); ``` **Note** again that this isn't the transform for ReScriptReact; ReScriptReact turns the final list into an array. But the idea still applies. So naturally, ` myChild ` is transformed to `React.jsx(MyComponent.make, {children: myChild})`. I.e. whatever you do, the arguments passed to the children position will be wrapped in a list. ## Usage See [ReScriptReact Elements & JSX](../react/elements-and-jsx.mdx) for an example application of JSX, which transforms the above calls into a ReScriptReact-specific call. Here's a JSX tag that shows most of the features. ```res nocheck
{React.string("hello")}
``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx(Playground$MyComponent, { children: JsxRuntime.jsx("div", { children: "hello", }), booleanAttribute: true, stringAttribute: "string", intAttribute: 1, forcedOptional: "hello", onClick: handleClick, }); ```
## Departures From JS JSX - Attributes and children don't mandate `{}`, but we show them anyway for ease of learning. Once you format your file, some of them go away and some turn into parentheses. - Props spread is supported, but there are some restrictions (see below). - Punning! - Props and tag names have to follow ReScript's restrictions on identifiers at the exception of hyphens for lowercase tags ([see below](#hyphens-in-tag-names)). ### Spread Props **Since 10.1** JSX props spread is supported with type safety. ```res nocheck ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx(Comp.make, { a: "a", b: "b", }); ``` Multiple spreads are not allowed: ```res nocheck ``` The spread must be at the first position, followed by other props: ```res nocheck ``` ### Punning "Punning" refers to the syntax shorthand for when a label and a value are the same. For example, in JavaScript, instead of doing `return {name: name}`, you can do `return {name}`. JSX supports punning. `` is just a shorthand for ``. The formatter will help you format to the punned syntax whenever possible. This is convenient in the cases where there are lots of props to pass down: ```res nocheck ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx(MyComponent, { text: text, isLoading: true, onClick: onClick, }); ``` Consequently, a JSX component can cram in a few more props before reaching for extra libraries solutions that avoids props passing. **Note** that this is a departure from ReactJS JSX, which does **not** have punning. ReactJS' `` desugars to ``, in order to conform to DOM's idioms and for backward compatibility. ### Hyphens in tag names **Since 11.1** JSX now supports lowercase tags with hyphens in their name. This allows to bind to web components. Note though that props names can't have hyphens, you should use `@as` to bind to such props in your custom `JsxDOM.domProps` type ([see generic JSX transform](#generic-jsx-transform-jsx-beyond-react-experimental)). ```res nocheck ``` ```js import * as JsxRuntime from "react/jsx-runtime"; JsxRuntime.jsx("model-viewer", { "touch-actions": "pan-y", src: src, }); ``` ## Generic JSX transform: JSX beyond React (experimental) **Since 11.1** While ReScript comes with first class support for JSX in React, it's also possible to have ReScript delegate JSX to other frameworks. You do that by configuring a _generic JSX transform_. This is what you need to do to use a generic JSX transform: 1. Make sure you have a ReScript module that [implements the functions and types necessary for the JSX transform](#implementing-a-generic-jsx-transform-module). 2. Configure `rescript.json` to delegated JSX to that module. That's it really. We'll expand on each point below. ### Configuration You configure a generic JSX transform by putting any module name in the `module` config of JSX in `rescript.json`. This can be _any valid module name_. Example part from `rescript.json`: ```json "jsx": { "module": "Preact" }, ``` This will now put the `Preact` module in control of the generated JSX calls. The `Preact` module can be defined by anyone - locally in your project, or by a package. As long a it's available in the global scope. The JSX transform will delegate any JSX related code to `Preact`. #### What about `@react.component` for components? `@react.component` will still be available, and so is a generic `@jsx.component` notation. Both work the same way. ### Usage Example Here's a quick usage example (the actual definition of `Preact.res` comes below): First, configure `rescript.json`: ```json "jsx": { "module": "Preact" }, ``` Now you can build Preact components: ```rescript // Name.res @jsx.component // or @react.component if you want let make = (~name) => Preact.string(`Hello ${name}!`) ``` And you can use them just like normal with JSX: ```rescript let name = ``` #### File level configuration You can configure what JSX transform is used at the file level via `@@jsxConfig`, just like before. Like: ```rescript @@jsxConfig({module_: "Preact"}) ``` This can be convenient if you're mixing different JSX frameworks in the same project. ### Implementing a generic JSX transform module Below is a full list of everything you need in a generic JSX transform module, including code comments to clarify. It's an example implementation of a `Preact` transform, so when doing this for other frameworks you'd of course adapt what you import from, and so on. > You can easily copy-paste-and-adapt this to your needs if you're creating bindings to a JSX framework. Most often, all you'll need to change is what the `@module("") external` points to, so the runtime calls point to the correct JS module. ```rescript // Preact.res /* Below is a number of aliases to the common `Jsx` module */ type element = Jsx.element type component<'props> = Jsx.component<'props> type componentLike<'props, 'return> = Jsx.componentLike<'props, 'return> @module("preact/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" @module("preact/jsx-runtime") external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx" @module("preact/jsx-runtime") external jsxs: (component<'props>, 'props) => element = "jsxs" @module("preact/jsx-runtime") external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs" /* These identity functions and static values below are optional, but lets you move things easily to the `element` type. The only required thing to define though is `array`, which the JSX transform will output. */ external array: array => element = "%identity" @val external null: element = "null" external float: float => element = "%identity" external int: int => element = "%identity" external string: string => element = "%identity" external promise: promise => element = "%identity" /* These are needed for Fragment (<> ) support */ type fragmentProps = {children?: element} @module("preact/jsx-runtime") external jsxFragment: component = "Fragment" /* The Elements module is the equivalent to the ReactDOM module in Preact. This holds things relevant to _lowercase_ JSX elements. */ module Elements = { /* Here you can control what props lowercase JSX elements should have. A base that the React JSX transform uses is provided via JsxDOM.domProps, but you can make this anything. The editor tooling will support autocompletion etc for your specific type. */ type props = JsxDOM.domProps @module("preact/jsx-runtime") external jsx: (string, props) => Jsx.element = "jsx" @module("preact/jsx-runtime") external div: (string, props) => Jsx.element = "jsx" @module("preact/jsx-runtime") external jsxKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsx" @module("preact/jsx-runtime") external jsxs: (string, props) => Jsx.element = "jsxs" @module("preact/jsx-runtime") external jsxsKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsxs" external someElement: element => option = "%identity" } ``` As you can see, most of the things you'll want to implement will be copy paste from the above. But do note that **everything needs to be there unless explicitly noted** or the transform will fail at compile time. To enable this, you need to configure the `jsx` `module` in your `rescript.json`: ```json { "jsx": { "version": 4, "module": "Preact" } } ``` _value "Preact" is the name of the module that implements the generic JSX transform._ ## Preserve mode **Since 12.0** JSX Preserve Mode keeps JSX syntax in the compiled JavaScript output instead of transforming it to `JsxRuntime.jsx` calls. This lets bundlers (ESBuild, SWC, Next.js) or React Server Components handle JSX transformation. ### Configuration ```json { "jsx": { "version": 4, "preserve": true } } ``` ```res nocheck let c1 =
{React.string("Hello")}
let c2 = ``` ```js let c1 =
{"Hello"}
; let c2 = ; ```
Note that the JSX output is functional but not always the most aesthetically pleasing. # Exception Exceptions are just a special kind of variant, thrown in **exceptional** cases (don't abuse them!). Consider using the [`option`](./null-undefined-option.mdx) or [`result`](/docs/manual/api/stdlib/result) type for recoverable errors. You can create your own exceptions like you'd make a variant (exceptions need to be capitalized too). ```res exception InputClosed(string) // later on throw(InputClosed("The stream has closed!")) ``` ```js import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; let InputClosed = /* @__PURE__ */ Primitive_exceptions.create( "_tempFile.InputClosed", ); throw { RE_EXN_ID: InputClosed, _1: "The stream has closed!", Error: new Error(), }; export { InputClosed }; ``` ## Built-in Exceptions ReScript has some built-in exceptions: ### `Not_found` ```res prelude let getItem = (item: int) => if (item === 3) { // return the found item here 1 } else { throw(Not_found) } let result = try { getItem(2) } catch { | Not_found => 0 // Default value if getItem throws } ``` ```js import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error(), }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } ``` Note that the above is just for demonstration purposes; in reality, you'd return an `option` directly from `getItem` and avoid the `try` altogether. You can directly match on exceptions _while_ getting another return value from a function: ```res prelude switch list{1, 2, 3}->List.getOrThrow(4) { | item => Console.log(item) | exception Not_found => Console.log("No such item found!") } ``` ```js import * as Stdlib_List from "./stdlib/Stdlib_List.js"; import * as Primitive_exceptions from "./stdlib/Primitive_exceptions.js"; let exit = 0; let item; try { item = Stdlib_List.getExn( { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }, 4, ); exit = 1; } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn; } } if (exit === 1) { console.log(item); } ``` ### `Invalid_argument` Used to check if argument is valid. This exception takes a string. ```res let divide = (a, b) => if b == 0 { throw(Invalid_argument("Denominator is zero")) } else { a / b } // catch error try divide(2, 0)->Console.log catch { | Invalid_argument(msg) => Console.log(msg) // Denominator is zero } ```` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error() }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow({ hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */0 } } }, 4); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } function divide(a, b) { if (b === 0) { throw { RE_EXN_ID: "Invalid_argument", _1: "Denominator is zero", Error: new Error() }; } return Primitive_int.div(a, b); } try { console.log(divide(2, 0)); } catch (raw_msg) { let msg = Primitive_exceptions.internalToException(raw_msg); if (msg.RE_EXN_ID === "Invalid_argument") { console.log(msg._1); } else { throw msg; } } export { getItem, result, divide, } ```` ### `Assert_failure` Thrown when you use `assert(condition)` and `condition` is false. The arguments are the location of the `assert` in the source code (file name, line number, column number). ```res let decodeUser = (json: JSON.t) => switch json { | Object(userDict) => switch (userDict->Dict.get("name"), userDict->Dict.get("age")) { | (Some(String(name)), Some(Number(age))) => (name, age->Float.toInt) | _ => assert(false) } | _ => assert(false) } try decodeUser(%raw("{}"))->Console.log catch { | Assert_failure(loc) => Console.log(loc) // ("filename", line, col) } ``` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error(), }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow( { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }, 4, ); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } function decodeUser(json) { if (typeof json === "object" && json !== null && !Array.isArray(json)) { let match = json["name"]; let match$1 = json["age"]; if (typeof match === "string" && typeof match$1 === "number") { return [match, match$1 | 0]; } throw { RE_EXN_ID: "Assert_failure", _1: ["_tempFile.res", 26, 11], Error: new Error(), }; } throw { RE_EXN_ID: "Assert_failure", _1: ["_tempFile.res", 28, 9], Error: new Error(), }; } try { console.log(decodeUser({})); } catch (raw_loc) { let loc = Primitive_exceptions.internalToException(raw_loc); if (loc.RE_EXN_ID === "Assert_failure") { console.log(loc._1); } else { throw loc; } } export { getItem, result, decodeUser }; ``` ### `Failure` Exception thrown to signal that the given arguments do not make sense. This exception takes a string as an argument. ```res let isValidEmail = email => { let hasAtSign = String.includes(email, "@") let hasDot = String.includes(email, ".") if !(hasAtSign && hasDot) { throw(Failure("Invalid email address")) } else { true } } let isValid = try isValidEmail("rescript.org") catch { | Failure(msg) => { Console.error(msg) false } } ```` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error() }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow({ hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */0 } } }, 4); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } function isValidEmail(email) { let hasAtSign = email.includes("@"); let hasDot = email.includes("."); if (hasAtSign && hasDot) { return true; } throw { RE_EXN_ID: "Failure", _1: "Invalid email address", Error: new Error() }; } let isValid; try { isValid = isValidEmail("rescript.org"); } catch (raw_msg) { let msg = Primitive_exceptions.internalToException(raw_msg); if (msg.RE_EXN_ID === "Failure") { console.error(msg._1); isValid = false; } else { throw msg; } } export { getItem, result, isValidEmail, isValid, } ```` ### `Division_by_zero` Exception thrown by integer division and remainder operations when their second argument is zero. ```res // ReScript throws `Division_by_zero` if the denominator is zero let result = try Some(10 / 0) catch { | Division_by_zero => None } Console.log(result) // None ```` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error() }; } try { getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID !== "Not_found") { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow({ hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */0 } } }, 4); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } let result; try { result = Primitive_int.div(10, 0); } catch (raw_exn$2) { let exn$2 = Primitive_exceptions.internalToException(raw_exn$2); if (exn$2.RE_EXN_ID === "Division_by_zero") { result = undefined; } else { throw exn$2; } } console.log(result); export { getItem, result, } ```` ## Catching JS Exceptions To distinguish between JavaScript exceptions and ReScript exceptions, ReScript namespaces JS exceptions under the `JsExn(payload)` variant. To catch an exception thrown from the JS side: Throw an exception from JS: ```js // Example.js exports.someJsFunctionThatThrows = () => { throw new Error("A Glitch in the Matrix!"); }; ``` Then catch it from ReScript: ```res nocheck // import the method in Example.js @module("./Example") external someJsFunctionThatThrows: () => unit = "someJsFunctionThatThrows" try { // call the external method someJSFunctionThatThrows() } catch { | JsExn(exn) => switch JsExn.message(exn) { | Some(m) => Console.log("Caught a JS exception! Message: " ++ m) | None => () } } ``` The payload `exn` here is of type `unknown` since in JS you can throw anything. To operate on `exn`, do like the code above by using the standard library's [`JsExn`](/docs/manual/api/stdlib/jsexn) module's helpers or use [`Type.Classify.classify`](/docs/manual/api/stdlib/type/classify#value-classify) to get more information about the runtime type of `exn`. ## Throw a JS Exception ### Throw a JS Error `throw(MyException)` throws a ReScript exception. To throw a JavaScript error (whatever your purpose is), use `JsError.throwWithMessage`: ```res let myTest = () => { JsError.throwWithMessage("Hello!") } ``` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error(), }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow( { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }, 4, ); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } function myTest() { return Stdlib_JsError.throwWithMessage("Hello!"); } export { getItem, result, myTest }; ``` Then you can catch it from the JS side: ```js // after importing `myTest`... try { myTest(); } catch (e) { console.log(e.message); // "Hello!" } ``` ### Throw a value that is not an JS Error If you want to throw any value that is not a valid JS Error, use `JsExn.throw`: ```res let myTest = () => { JsExn.throw("some non-error value!") } ``` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error(), }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow( { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }, 4, ); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } function myTest() { throw "some non-error value!"; } export { getItem, result, myTest }; ``` Then you can catch it from the JS side: ```js // after importing `myTest`... try { myTest(); } catch (message) { console.log(message); // "Hello!" } ``` ## Catch ReScript Exceptions from JS To let JavaScript code work with exception-throwing ReScript code, you don't need to throw a JS exception. ReScript exceptions can be used directly from JavaScript. ```res exception BadArgument({myMessage: string}) let myTest = () => { throw(BadArgument({myMessage: "Oops!"})) } ``` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function getItem(item) { if (item === 3) { return 1; } throw { RE_EXN_ID: "Not_found", Error: new Error(), }; } let result; try { result = getItem(2); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = 0; } else { throw exn; } } let exit = 0; let item; try { item = Stdlib_List.getOrThrow( { hd: 1, tl: { hd: 2, tl: { hd: 3, tl: /* [] */ 0, }, }, }, 4, ); exit = 1; } catch (raw_exn$1) { let exn$1 = Primitive_exceptions.internalToException(raw_exn$1); if (exn$1.RE_EXN_ID === "Not_found") { console.log("No such item found!"); } else { throw exn$1; } } if (exit === 1) { console.log(item); } let BadArgument = /* @__PURE__ */ Primitive_exceptions.create( "_tempFile.BadArgument", ); function myTest() { throw { RE_EXN_ID: BadArgument, myMessage: "Oops!", Error: new Error(), }; } export { getItem, result, BadArgument, myTest }; ``` Then, in your JS: ```js // after importing `myTest`... try { myTest(); } catch (e) { console.log(e.myMessage); // "Oops!" console.log(e.Error.stack); // the stack trace } ``` The above `BadArgument` exception takes an inline record type. We special-case compile the exception as `{RE_EXN_ID, myMessage, Error}` for good ergonomics. If the exception instead took ordinary positional arguments, l like the standard library's `Invalid_argument("Oops!")`, which takes a single argument, the argument is compiled to JS as the field `_1` instead. A second positional argument would compile to `_2`, etc. ## Tips & Tricks When you have ordinary variants, you often don't **need** exceptions. For example, instead of throwing when `item` can't be found in a collection, try to return an `option` (`None` in this case) instead. ### Catch Both ReScript and JS Exceptions in the Same `catch` Clause ```res nocheck try { someOtherJSFunctionThatThrows() } catch { | Not_found => ... // catch a ReScript exception | Invalid_argument(_) => ... // catch a second ReScript exception | JsExn(exn) => ... // catch the JS exception } ``` This technically works, but hopefully you don't ever have to work with such code... # Lazy Value If you have some expensive computations you'd like to **defer and cache** subsequently, you can turn them into _lazy_ values: ```res prelude @module("node:fs") external readdirSync: string => array = "readdirSync" // Read the directory, only once let expensiveFilesRead = Lazy.make(() => { Console.log("Reading dir") readdirSync("./pages") }) ``` ```js import * as Lazy from "./stdlib/Lazy.js"; import * as Nodefs from "node:fs"; let expensiveFilesRead = Lazy.make(() => { console.log("Reading dir"); return Nodefs.readdirSync("./pages"); }); ``` **Note**: a lazy value is **not** a [shared data type](./shared-data-types.mdx). Don't rely on its runtime representation in your JavaScript code. ## Execute The Lazy Computation To actually run the lazy value's computation, use `Lazy.get` from the standard library `Lazy` module: ```res // First call. The computation happens Console.log(Lazy.get(expensiveFilesRead)) // logs "Reading dir" and the directory content // Second call. Will just return the already calculated result Console.log(Lazy.get(expensiveFilesRead)) // logs the directory content ``` ```js import * as Nodefs from "node:fs"; import * as Stdlib_Lazy from "@rescript/runtime/lib/es6/Stdlib_Lazy.js"; let expensiveFilesRead = Stdlib_Lazy.make(() => { console.log("Reading dir"); return Nodefs.readdirSync("./pages"); }); console.log(Stdlib_Lazy.get(expensiveFilesRead)); console.log(Stdlib_Lazy.get(expensiveFilesRead)); export { expensiveFilesRead }; ``` The first time `Lazy.get` is called, the expensive computation happens and the result is **cached**. The second time, the cached value is directly used. **You can't re-trigger the computation after the first `get` call**. Make sure you only use a lazy value with computations whose results don't change (e.g. an expensive server request whose response is always the same). ## Exception Handling For completeness' sake, our files read example might throw an exception because of `readdirSync`. Here's how you'd handle it: ```res let result = try { Lazy.get(expensiveFilesRead) } catch { | Not_found => [] // empty array of files } ``` ```js import * as Nodefs from "node:fs"; import * as Stdlib_Lazy from "@rescript/runtime/lib/es6/Stdlib_Lazy.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; let expensiveFilesRead = Stdlib_Lazy.make(() => { console.log("Reading dir"); return Nodefs.readdirSync("./pages"); }); let result; try { result = Stdlib_Lazy.get(expensiveFilesRead); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); if (exn.RE_EXN_ID === "Not_found") { result = []; } else { throw exn; } } export { expensiveFilesRead, result }; ``` Though you should probably handle the exception inside the lazy computation itself. # Promise > **Note:** Starting from ReScript 10.1 and above, we recommend using [async / await](./async-await.mdx) when interacting with Promises. ## `promise` type **Since 10.1** In ReScript, every JS promise is represented with the globally available `promise<'a>` type. Here's a usage example in a function signature: ```resi // User.resi file type user = {name: string} let fetchUser: string => promise ``` To work with promise values (instead of using `async` / `await`) you may want to use the built-in `Promise` module. ## Promise A builtin module to create, chain and manipulate promises. ### Creating a promise ```res let p1 = Promise.make((resolve, reject) => { resolve("hello world") }) let p2 = Promise.resolve("some value") // You can only reject `exn` values for streamlined catch handling exception MyOwnError(string) let p3 = Promise.reject(MyOwnError("some rejection")) ``` ### Access the contents and transform a promise ```res let logAsyncMessage = () => { open Promise Promise.resolve("hello world") ->then(msg => { // then callbacks require the result to be resolved explicitly resolve("Message: " ++ msg) }) ->then(msg => { Console.log(msg) // Even if there is no result, we need to use resolve() to return a promise resolve() }) ->ignore // Requires ignoring due to unhandled return value } ``` For comparison, the `async` / `await` version of the same code would look like this: ```res let logAsyncMessage = async () => { let msg = await Promise.resolve("hello world") Console.log(`Message: ${msg}`) } ``` Needless to say, the async / await version offers better ergonomics and less opportunities to run into type issues. ### Handling Rejected Promises You can handle a rejected promise using the [`Promise.catch()`](/docs/manual/api/stdlib/promise#value-catch) method, which allows you to catch and manage errors effectively. ### Run multiple promises in parallel In case you want to launch multiple promises in parallel, use `Promise.all`: ```res @val external fetchMessage: string => promise = "global.fetchMessage" let logAsyncMessage = async () => { let messages = await Promise.all([fetchMessage("message1"), fetchMessage("message2")]) Console.log(messages->Array.joinWith(", ")) } ``` ```js async function logAsyncMessage() { let messages = await Promise.all([ global.fetchMessage("message1"), global.fetchMessage("message2"), ]); console.log(messages.join(", ")); } export { logAsyncMessage }; ``` {/* This prelude is used in many different followup examples, so we use it to shorten the noise of the example code. */}
```res prelude @val external fetchUserMail: string => promise = "GlobalAPI.fetchUserMail" @val external sendAnalytics: string => promise = "GlobalAPI.sendAnalytics" ```
{/* See https://github.com/cristianoc/rescript-compiler-experiments/pull/1#issuecomment-1131182023 for all async/await use-case examples */} # Async / Await ReScript comes with `async` / `await` support to make asynchronous, `Promise` based code easier to read and write. This feature is very similar to its JS equivalent, so if you are already familiar with JS' `async` / `await`, you will feel right at home. ## How it looks Let's start with a quick example to show-case the syntax: ```res // Some fictive functionality that offers asynchronous network actions @val external fetchUserMail: string => promise = "GlobalAPI.fetchUserMail" @val external sendAnalytics: string => promise = "GlobalAPI.sendAnalytics" // We use the `async` keyword to allow the use of `await` in the function body let logUserDetails = async (userId: string) => { // We use `await` to fetch the user email from our fictive user endpoint let email = await fetchUserMail(userId) await sendAnalytics(`User details have been logged for ${userId}`) Console.log(`Email address for user ${userId}: ${email}`) } ``` ```js async function logUserDetails(userId) { let email = await GlobalAPI.fetchUserMail(userId); await GlobalAPI.sendAnalytics(`User details have been logged for ` + userId); console.log(`Email address for user ` + userId + `: ` + email); } export { logUserDetails }; ``` As we can see above, an `async` function is defined via the `async` keyword right before the function's parameter list. In the function body, we are now able to use the `await` keyword to explicitly wait for a `Promise` value and assign its content to a let binding `email`. You will probably notice that this looks very similar to `async` / `await` in JS, but there are still a few details that are specific to ReScript. The next few sections will go through all the details that are specific to the ReScript type system. ## Basics - You may only use `await` in `async` function bodies - `await` may only be called on a `promise` value - `await` calls are expressions, therefore they can be used in pattern matching (`switch`) - A function returning a `promise<'a>` is equivalent to an `async` function returning a value `'a` (important for writing signature files and bindings) - `promise` values and types returned from an `async` function don't auto-collapse into a flat promise. See the details below. ## Types and `async` functions ### `async` function type signatures Function type signatures (i.e defined in signature files) don't require any special keywords for `async` usage. Whenever you want to type an `async` function, use a `promise` return type. ```resi // Demo.resi let fetchUserMail: string => promise ``` The same logic applies to type definitions in `.res` files: ```res // function type type someAsyncFn = int => promise // Function type annotation let fetchData: string => promise = async (userId) => { await fetchUserMail(userId) } ``` **BUT:** When typing `async` functions in your implementation files, you need to omit the `promise<'a>` type: ```res // This function is compiled into a `string => promise` type. // The promise<...> part is implicitly added by the compiler. let fetchData = async (userId: string): string => { await fetchUserMail("test") } ``` For completeness reasons, let's expand the full signature and inline type definitions in one code snippet: ```res nocheck // Note how the inline return type uses `string`, while the type definition uses `promise` let fetchData: string => promise = async (userId: string): string { await fetchUserMail(userId) } ``` **Note:** In a practical scenario you'd either use a type signature, or inline types, not both at the same time. In case you are interested in the design decisions, check out [this discussion](https://github.com/rescript-lang/rescript-compiler/pull/5913#issuecomment-1359003870). ### Promises don't auto-collapse in async functions In JS, nested promises (i.e. `promise>`) will automatically collapse into a flat promise (`promise<'a>`). This is not the case in ReScript. Use the `await` function to manually unwrap any nested promises within an `async` function instead. ```res let fetchData = async (userId: string): string => { // We can't just return the result of `fetchUserMail`, otherwise we'd get a // type error due to our function return type of type `string` await fetchUserMail(userId) } ``` ## Error handling You may use `try / catch` or `switch` to handle exceptions during async execution. ```res // For simulation purposes let authenticate = async () => { JsError.RangeError.throwWithMessage("Authentication failed.") } let checkAuth = async () => { try { await authenticate() } catch { | JsExn(e) => switch JsExn.message(e) { | Some(msg) => Console.log("JS error thrown: " ++ msg) | None => Console.log("Some other exception has been thrown") } } } ``` Note how we are essentially catching JS errors the same way as described in our [Exception](./exception.mdx#catch-rescript-exceptions-from-js) section. You may unify error and value handling in a single switch as well: ```res let authenticate = async () => { JsError.RangeError.throwWithMessage("Authentication failed.") } let checkAuth = async () => { switch await authenticate() { | _ => Console.log("ok") | exception JsExn(e) => switch JsExn.message(e) { | Some(msg) => Console.log("JS error thrown: " ++ msg) | None => Console.log("Some other exception has been thrown") } } } ``` **Important:** When using `await` with a `switch`, always make sure to put the actual await call in the `switch` expression, otherwise your `await` error will not be caught. ## Piping `await` calls You may want to pipe the result of an `await` call right into another function. This can be done by wrapping your `await` calls in a new `{}` closure. ```res @val external fetchUserMail: string => promise = "GlobalAPI.fetchUserMail" let fetchData = async () => { let mail = {await fetchUserMail("1234")}->String.toUpperCase Console.log(`All upper-cased mail: ${mail}`) } ``` ```js async function fetchData() { let mail = (await GlobalAPI.fetchUserMail("1234")).toUpperCase(); console.log(`All upper-cased mail: ` + mail); } export { fetchData }; ``` Note how the original closure was removed in the final JS output. No extra allocations! ## Pattern matching on `await` calls `await` calls are just another kind of expression, so you can use `switch` pattern matching for more complex logic. ```res @val external fetchUserMail: string => promise = "GlobalAPI.fetchUserMail" let fetchData = async () => { switch (await fetchUserMail("user1"), await fetchUserMail("user2")) { | (user1Mail, user2Mail) => { Console.log("user 1 mail: " ++ user1Mail) Console.log("user 2 mail: " ++ user2Mail) } | exception JsExn(err) => Console.log2("Some error occurred", err) } } ``` ```js import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; async function fetchData() { let val; let val$1; try { val = await GlobalAPI.fetchUserMail("user1"); val$1 = await GlobalAPI.fetchUserMail("user2"); } catch (raw_err) { let err = Primitive_exceptions.internalToException(raw_err); if (err.RE_EXN_ID === "JsExn") { console.log("Some error occurred", err._1); return; } throw err; } console.log("user 1 mail: " + val); console.log("user 2 mail: " + val$1); } export { fetchData }; ``` ## `await` multiple promises We can utilize the `Promise` module to handle multiple promises. E.g. let's use `Promise.all` to wait for multiple promises before continuing the program: ```res let pauseReturn = (value, timeout) => { Promise.make((resolve, _reject) => { setTimeout(() => { resolve(value) }, timeout)->ignore }) } let logMultipleValues = async () => { let promise1 = pauseReturn("value1", 2000) let promise2 = pauseReturn("value2", 1200) let promise3 = pauseReturn("value3", 500) let all = await Promise.all([promise1, promise2, promise3]) switch all { | [v1, v2, v3] => Console.log(`All values: ${v1}, ${v2}, ${v3}`) | _ => Console.log("this should never happen") } } ``` ## JS Interop with `async` functions `async` / `await` practically works with any function that returns a `promise<'a>` value. Map your `promise` returning function via an `external`, and use it in an `async` function as usual. Here's a full example of using the MDN `fetch` API, using `async` / `await` to simulate a login: ```res nocheck // A generic Response type for typing our fetch requests module Response = { type t<'data> @send external json: t<'data> => promise<'data> = "json" } // A binding to our globally available `fetch` function. `fetch` is a // standardized function to retrieve data from the network that is available in // all modern browsers. @val @scope("globalThis") external fetch: ( string, 'params, ) => promise, "error": Nullable.t}>> = "fetch" // We now use our asynchronous `fetch` function to simulate a login. // Note how we use `await` with regular functions returning a `promise`. let login = async (email: string, password: string) => { let body = { "email": email, "password": password, } let params = { "method": "POST", "headers": { "Content-Type": "application/json", }, "body": Json.stringifyAny(body), } try { let response = await fetch("https://reqres.in/api/login", params) let data = await response->Response.json switch Nullable.toOption(data["error"]) { | Some(msg) => Error(msg) | None => switch Nullable.toOption(data["token"]) { | Some(token) => Ok(token) | None => Error("Didn't return a token") } } } catch { | _ => Error("Unexpected network error occurred") } } ``` # Tagged templates **Since 11.1** Tagged templates provide a special form of string interpolation, enabling the creation of template literals where placeholders aren't restricted to strings. Moreover, the resulting output isn't confined solely to strings either. You can take a look at the [JS documentation about tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) to learn more about them. ## Define a tag function Tag functions in ReScript have the following signature: ```res sig let myTagFunction : (array, array<'param>) => 'output ``` As you can see, you can have any type you want both for the placeholder array and for the output. Given how string interpolation works, you'll always have the following invariant: ```res nocheck Array.length(strings) == Array.length(placeholder) + 1 ``` Let's say you want to interpolate strings with all kind of builtin types and make it work inside React components, you can define the following tag function: ```res prelude type params = | I(int) | F(float) | S(string) | Bool(bool) let s = (strings, parameters) => { let text = Array.reduceWithIndex(parameters, Array.getUnsafe(strings, 0), ( acc, param, i, ) => { let s = Array.getUnsafe(strings, i + 1) let p = switch param { | I(i) => Int.toString(i) | F(f) => Float.toString(f) | S(s) => s | Bool(true) => "true" | Bool(false) => "false" } acc ++ p ++ s }) React.string(text) } ``` ```js import * as Core__Array from "./stdlib/core__Array.js"; function s(strings, parameters) { return Core__Array.reduceWithIndex( parameters, strings[0], function (acc, param, i) { var s = strings[(i + 1) | 0]; var p; switch (param.TAG) { case "I": case "F": p = param._0.toString(); break; case "S": p = param._0; break; case "Bool": p = param._0 ? "true" : "false"; break; } return acc + p + s; }, ); } ``` ## Write tagged template literals Now that you have defined your tag function, you can use it this way: ```res module Greetings = { @react.component let make = (~name, ~age) => {
{s`hello ${S(name)} you're ${I(age)} year old!`}
} } ``` ```js import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js"; import * as JsxRuntime from "react/jsx-runtime"; function s(strings, parameters) { return Stdlib_Array.reduceWithIndex( parameters, strings[0], (acc, param, i) => { let s = strings[(i + 1) | 0]; let p; switch (param.TAG) { case "I": case "F": p = param._0.toString(); break; case "S": p = param._0; break; case "Bool": p = param._0 ? "true" : "false"; break; } return acc + p + s; }, ); } function Example$Greetings(props) { return JsxRuntime.jsx("div", { children: s( [`hello `, ` you're `, ` year old!`], [ { TAG: "S", _0: props.name, }, { TAG: "I", _0: props.age, }, ], ), }); } let Greetings = { make: Example$Greetings, }; export { s, Greetings }; ``` ```jsx import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js"; import * as JsxRuntime from "react/jsx-runtime"; function s(strings, parameters) { return Stdlib_Array.reduceWithIndex( parameters, strings[0], (acc, param, i) => { let s = strings[(i + 1) | 0]; let p; switch (param.TAG) { case "I": case "F": p = param._0.toString(); break; case "S": p = param._0; break; case "Bool": p = param._0 ? "true" : "false"; break; } return acc + p + s; }, ); } function Example$Greetings(props) { return (
{s( [`hello `, ` you're `, ` year old!`], [ { TAG: "S", _0: props.name, }, { TAG: "I", _0: props.age, }, ], )}
); } let Greetings = { make: Example$Greetings, }; export { s, Greetings }; ```
Pretty neat, isn't it? As you can see, it looks like any regular template literal but it accepts placeholders that are not strings and it outputs something that is not a string either, a `React.element` in this case. # Module ## Basics **Modules are like mini files**! They can contain type definitions, `let` bindings, nested modules, etc. ### Creation To create a module, use the `module` keyword. The module name must start with a **capital letter**. Whatever you could place in a `.res` file, you may place inside a module definition's `{}` block. ```res nocheck module School = { type profession = Teacher | Director let person1 = Teacher let getProfession = (person) => switch person { | Teacher => "A teacher" | Director => "A director" } } ``` ```js function getProfession(person) { if (person === "Teacher") { return "A teacher"; } else { return "A director"; } } let School = { person1: "Teacher", getProfession: getProfession, }; export { School }; ``` A module's contents (including types!) can be accessed much like a record's, using the `.` notation. This demonstrates modules' utility for namespacing. ```res nocheck let anotherPerson: School.profession = School.Teacher Console.log(School.getProfession(anotherPerson)) /* "A teacher" */ ``` ```js var anotherPerson = /* Teacher */ 0; console.log("A teacher"); ``` Nested modules work too. ```res nocheck module MyModule = { module NestedModule = { let message = "hello" } } let message = MyModule.NestedModule.message ``` ```js let message = "hello"; let NestedModule = { message: message, }; let MyModule = { NestedModule: NestedModule, }; export { MyModule, message }; ``` ### `open`ing a module Constantly referring to a value/type in a module can be tedious. Instead, we can "open" a module and refer to its contents without always prepending them with the module's name. Instead of writing: ```res nocheck let p = School.getProfession(School.person1) ``` ```js var p = School.getProfession(School.person1); ``` We can write: ```res nocheck open School let p = getProfession(person1) ``` ```js var p = School.getProfession(School.person1); ``` The content of `School` module are made visible (**not** copied into the file, but simply made visible!) in scope. `profession`, `getProfession` and `person1` will thus correctly be found. **Use `open` this sparingly, it's convenient, but makes it hard to know where some values come from**. You should usually use `open` in a local scope: ```res nocheck let p = { open School getProfession(person1) } /* School's content isn't visible here anymore */ ``` ```js var p = School.getProfession(School.person1); ``` ### Use `open!` to ignore shadow warnings There are situations where `open` will cause a warning due to existing identifiers (bindings, types) being redefined. Use `open!` to explicitly tell the compiler that this is desired behavior. ```res nocheck let map = (arr, value) => { value } // opening Array would shadow our previously defined `map` // `open!` will explicitly turn off the automatic warning open! Array let arr = map([1,2,3], (a) => { a + 1}) ``` **Note:** Same as with `open`, don't overuse `open!` statements if not necessary. Use (sub)modules to prevent shadowing issues. ### Destructuring modules **Since 9.0.2** As an alternative to `open`ing a module, you can also destructure a module's functions and values into separate let bindings (similarly on how we'd destructure an object in JavaScript). ```res nocheck module User = { let user1 = "Anna" let user2 = "Franz" } // Destructure by name let {user1, user2} = module(User) // Destructure with different alias let {user1: anna, user2: franz} = module(User) ``` ```js var user1 = "Anna"; var user2 = "Franz"; var User = { user1: user1, user2: user2, }; ``` **Note:** You can't extract types with module destructuring β€” use a type alias instead (`type user = User.myUserType`). ### Extending modules Using `include` in a module statically "spreads" a module's content into a new one, thus often fulfill the role of "inheritance" or "mixin". **Note**: this is equivalent to a compiler-level copy paste. **We heavily discourage `include`**. Use it as last resort! ```res nocheck module BaseComponent = { let defaultGreeting = "Hello" let getAudience = (~excited) => excited ? "world!" : "world" } module ActualComponent = { /* the content is copied over */ include BaseComponent /* overrides BaseComponent.defaultGreeting */ let defaultGreeting = "Hey" let render = () => defaultGreeting ++ " " ++ getAudience(~excited=true) } ``` ```js function getAudience(excited) { if (excited) { return "world!"; } else { return "world"; } } let BaseComponent = { defaultGreeting: "Hello", getAudience: getAudience, }; let defaultGreeting = "Hey"; function render() { return defaultGreeting + " world!"; } let ActualComponent = { getAudience: getAudience, defaultGreeting: defaultGreeting, render: render, }; export { BaseComponent, ActualComponent }; ``` **Note**: `open` and `include` are very different! The former brings a module's content into your current scope, so that you don't have to refer to a value by prefixing it with the module's name every time. The latter **copies over** the definition of a module statically, then also do an `open`. ### Every `.res` file is a module Every ReScript file is itself compiled to a module of the same name as the file name, capitalized. The file `React.res` implicitly forms a module `React`, which can be seen by other source files. **Note**: ReScript file names should, by convention, be capitalized so that their casing matches their module name. Uncapitalized file names are not invalid, but will be implicitly transformed into a capitalized module name. I.e. `file.res` will be compiled into the module `File`. To simplify and minimize the disconnect here, the convention is therefore to capitalize file names. ## Signatures A module's type is called a "signature", and can be written explicitly. If a module is like a `.res` (implementation) file, then a module's signature is like a `.resi` (interface) file. ### Creation To create a signature, use the `module type` keyword. The signature name must start with a **capital letter**. Whatever you could place in a `.resi` file, you may place inside a signature definition's `{}` block. ```res nocheck /* Using the types defined above */ module type EstablishmentType = { type profession let getProfession: profession => string } ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` A signature defines the list of requirements that a module must satisfy in order for that module to match the signature. Those requirements are of the form: - `let x: int` requires a `let` binding named `x`, of type `int`. - `type t = someType` requires a type field `t` to be equal to `someType`. - `type t` requires a type field `t`, but without imposing any requirements on the actual, concrete type of `t`. We'd use `t` in other entries in the signature to describe relationships, e.g. `let makePair: t => (t, t)` but we cannot, for example, assume that `t` is an `int`. This gives us great, enforced abstraction abilities. To illustrate the various kinds of type entries, consider the above signature `EstablishmentType` which requires that a module: - Declare a type named `profession`. - Must include a function that takes in a value of the type `profession` and returns a string. **Note**: Modules of the type `EstablishmentType` can contain more fields than the signature declares, just like the module `School` defined above (if we choose to assign it the type `EstablishmentType`. Otherwise, `School` exposes every field). This effectively makes the `person1` field an enforced implementation detail! Outsiders can't access it, since it's not present in the signature; the signature **constrained** what others can access. The type `EstablishmentType.profession` is **abstract**: it doesn't have a concrete type; it's saying "I don't care what the actual type is, but it's used as input to `getProfession`". This is useful to fit many modules under the same interface: ```res nocheck module Company: EstablishmentType = { type profession = CEO | Designer | Engineer | ... let getProfession = (person) => ... let person1 = ... let person2 = ... } ``` ```js function getProfession(person) { ... } var person1 = ... var person2 = ... var Company = { getProfession: getProfession, person1: person1, person2: person2 }; ``` It's also useful to hide the underlying type as an implementation detail others can't rely on. If you ask what the type of `Company.profession` is, instead of exposing the variant, it'll only tell you "it's `Company.profession`". This also means that the compiler can't make assumptions about the type. In certain cases, when working with abstract types and `option` for example, the compiler doesn't know whether the abstract type can be the JavaScript value `undefined` or not. This can lead to less optimal code being generated. For this reason, you can use the `@notUndefined` decorator to tell the compiler that the abstract type can never be `undefined` (use with caution and see the `@notUndefined` decorator documentation for more details and caveats). ### Extending module signatures Like modules themselves, module signatures can also be extended by other module signatures using `include`. Again, **heavily discouraged**: ```res nocheck module type BaseComponent = { let defaultGreeting: string let getAudience: (~excited: bool) => string } module type ActualComponent = { /* the BaseComponent signature is copied over */ include BaseComponent let render: unit => string } ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` **Note**: `BaseComponent` is a module **type**, not an actual module itself! If you do not have a defined module type, you can extract it from an actual module using `include (module type of ActualModuleName)`. For example, we can extend the `List` module from the standard library, which does not define a module type. ```res nocheck module type MyList = { include (module type of List) let myListFun: list<'a> => list<'a> } ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ### Every `.resi` file is a signature Similar to how a `React.res` file implicitly defines a module `React`, a file `React.resi` implicitly defines a signature for `React`. If `React.resi` isn't provided, the signature of `React.res` defaults to exposing all the fields of the module. Because they don't contain implementation files, `.resi` files are used in the ecosystem to also document the public API of their corresponding modules. ```res nocheck /* file React.res (implementation. Compiles to module React) */ type state = int let render = (str) => str ``` ```js function render(str) { return str; } export { render }; ``` ```res sig /* file React.resi (interface. Compiles to the signature of React.res) */ type state = int let render: string => string ``` ## Module Functions (functors) Modules can be passed to functions! It would be the equivalent of passing a file as a first-class item. However, modules are at a different "layer" of the language than other common concepts, so we can't pass them to _regular_ functions. Instead, we pass them to special functions called "functors". The syntax for defining and using functors is very much like the syntax for defining and using regular functions. The primary differences are: - Functors use the `module` keyword instead of `let`. - Functors take modules as arguments and return a module. - Functors _require_ annotating arguments. - Functors must start with a capital letter (just like modules/signatures). Here's an example `MakeSet` functor, that takes in a module of the type `Comparable` and returns a new set that can contain such comparable items. ```res prelude module type Comparable = { type t let equal: (t, t) => bool } module MakeSet = (Item: Comparable) => { // let's use a list as our naive backing data structure type backingType = list let empty = list{} let add = (currentSet: backingType, newItem: Item.t): backingType => // if item exists if currentSet->List.some(x => Item.equal(x, newItem)) { currentSet // return the same (immutable) set (a list really) } else { list{ newItem, ...currentSet // prepend to the set and return it } } } ``` ```js var List = require("./stdlib/list.js"); function MakeSet(Item) { var add = function (currentSet, newItem) { if ( List.exists(function (x) { return Item.equal(x, newItem); }, currentSet) ) { return currentSet; } else { return { hd: newItem, tl: currentSet, }; } }; return { empty: /* [] */ 0, add: add, }; } ``` Functors can be applied using function application syntax. In this case, we're creating a set, whose items are pairs of integers. ```res nocheck module IntPair = { type t = (int, int) let equal = ((x1: int, y1: int), (x2, y2)) => x1 == x2 && y1 == y2 let create = (x, y) => (x, y) } /* IntPair abides by the Comparable signature required by MakeSet */ module SetOfIntPairs = MakeSet(IntPair) ``` ```js import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js"; function MakeSet(Item) { let add = (currentSet, newItem) => { if (Stdlib_List.some(currentSet, (x) => Item.equal(x, newItem))) { return currentSet; } else { return { hd: newItem, tl: currentSet, }; } }; return { empty: /* [] */ 0, add: add, }; } function equal(param, param$1) { if (param[0] === param$1[0]) { return param[1] === param$1[1]; } else { return false; } } function create(x, y) { return [x, y]; } let IntPair = { equal: equal, create: create, }; function add(currentSet, newItem) { if (Stdlib_List.some(currentSet, (x) => equal(x, newItem))) { return currentSet; } else { return { hd: newItem, tl: currentSet, }; } } let SetOfIntPairs = { empty: /* [] */ 0, add: add, }; export { MakeSet, IntPair, SetOfIntPairs }; ``` ### Module functions types Like with module types, functor types also act to constrain and hide what we may assume about functors. The syntax for functor types are consistent with those for function types, but with types capitalized to represent the signatures of modules the functor accepts as arguments and return values. In the previous example, we're exposing the backing type of a set; by giving `MakeSet` a functor signature, we can hide the underlying data structure! ```res nocheck module type Comparable = ... module type MakeSetType = (Item: Comparable) => { type backingType let empty: backingType let add: (backingType, Item.t) => backingType } module MakeSet: MakeSetType = (Item: Comparable) => { ... } ``` ```js // Empty output ``` ## Exotic Module Filenames **Since 8.3** It is possible to use non-conventional characters in your filenames (which is sometimes needed for specific JS frameworks). Here are some examples: - `src/Button.ios.res` - `pages/[id].res` Please note that modules with an exotic filename will not be accessible from other ReScript modules and will only produce JavaScript files. ## Tips & Tricks Modules and functors are at a different "layer" of language than the rest (functions, let bindings, data structures, etc.). For example, you can't easily pass them into a tuple or record. Use them judiciously, if ever! Lots of times, just a record or a function is enough. # Import & Export ## Import a Module/File Unlike JavaScript, ReScript doesn't have or need import statements: ```res nocheck // Inside School.res let studentMessage = Student.message ``` ```js var Student = require("./Student.res.js"); var studentMessage = Student.message; ``` The above code refers to the `message` binding in the file `Student.res`. Every ReScript file is also a module, so accessing another file's content is the same as accessing another module's content! A ReScript project's file names need to be unique. ## Export Stuff By default, every file's type declaration, binding and module is exported, aka publicly usable by another file. **This also means those values, once compiled into JS, are immediately usable by your JS code**. To only export a few selected things, use a `.resi` [interface file](./module.mdx#signatures). ## Work with JavaScript Import & Export To see how to import JS modules and export stuff for JS consumption, see the JavaScript Interop section's [Import from/Export to JS](./import-from-export-to-js.mdx). # Attribute (Decorator) Like many other languages, ReScript allows annotating a piece of code to express extra functionality. Here's an example: ```res @inline let mode = "dev" let mode2 = mode ``` ```js let mode2 = "dev"; export { mode2 }; ``` The `@inline` annotation tells `mode`'s value to be inlined into its usage sites (see output). We call such annotation "attribute" (or "decorator" in JavaScript). An attribute starts with `@` and goes before the item it annotates. In the above example, it's hooked onto the let binding. ## Usage > **Note:** In previous versions (< 8.3) all our interop related attributes started with a `bs.` prefix (`bs.module`, `bs.val`). Our formatter will automatically drop them in newer ReScript versions. You can put an attribute almost anywhere. You can even add extra data to them by using them visually like a function call. Here are a few famous attributes (explained in other sections): ```res @@warning("-27") @unboxed type a = Name(string) @val external message: string = "message" type student = { age: int, @as("aria-label") ariaLabel: string, } @deprecated let customDouble = foo => foo * 2 @deprecated("Use SomeOther.customTriple instead") let customTriple = foo => foo * 3 ``` ```js function customDouble(foo) { return foo << 1; } function customTriple(foo) { return (foo * 3) | 0; } export { customDouble, customTriple }; ``` 1. `@@warning("-27")` is a standalone attribute that annotates the entire file. Those attributes start with `@@`. Here, it carries the data `"-27"`. You can find a full list of all available warnings [here](./warning-numbers.mdx). 2. `@unboxed` annotates the type definition. 3. `@val` annotates the `external` statement. 4. `@as("aria-label")` annotates the `ariaLabel` record field. 5. `@deprecated` annotates the `customDouble` expression. This shows a warning while compiling telling consumers to not rely on this method long-term. 6. `@deprecated("Use SomeOther.customTriple instead")` annotates the `customTriple` expression with a string to describe the reason for deprecation. For a list of all decorators and their usage, please refer to the [Syntax Lookup](../../syntax-lookup/) page. ## Extension Point There's a second category of attributes, called "extension points" (a remnant term of our early systems): ```res %raw("var a = 1") ``` ```js ((var a = 1)); ``` Extension points are attributes that don't _annotate_ an item; they _are_ the item. Usually they serve as placeholders for the compiler to implicitly substitute them with another item. Extension points start with `%`. A standalone extension point (akin to a standalone regular attribute) starts with `%%`. For a list of all extension points and their usage, please refer to the [Syntax Lookup](../../syntax-lookup/) page. # Reserved Keywords > **Note**: Some of these words are reserved purely for backward compatibility. > > If you _need_ to use one of these names as binding and/or field name, see [Use Illegal Identifier Names](./use-illegal-identifier-names.mdx). - `and` - `as` - `assert` {/* - `begin` */} {/* - `class` */} - `constraint` {/* - `do` */} {/* - `done` */} - `else` {/* - `end` */} {/* - `esfun` */} - `exception` - `external` * `false` * `for` {/* - `fun` */} {/* - `function` */} {/* - `functor` */} - `if` - `in` - `include` {/* - `inherit` */} {/* - `initializer` */} * `lazy` * `let` - `module` - `mutable` {/* - `new` */} {/* - `nonrec` */} {/* - `object` */} - `of` - `open` {/* - `or` */} {/* - `pri` */} {/* - `pub` */} - `rec` {/* - `sig` */} {/* - `struct` */} - `switch` {/* - `then` */} - `true` - `try` - `type` {/* - `val` */} {/* - `virtual` */} - `when` - `while` - `with` # Equality and Comparison ReScript has shallow equality `===`, deep equality `==`, and comparison operators `>`, `>=`, `<`, and `<=`. ## Shallow equality The shallow equality operator `===` compares two values and either compiles to `===` or a `bool` if the equality is known to the compiler. It behaves the same as the strict equality operator `===` in JavaScript. Using `===` will never add a runtime cost. ```res let t1 = 1 === 1 // true let t2 = "foo" === "foo" // true let t3 = { "foo": "bar" } === { "foo": "bar"} // false let doStringsMatch = (s1: string, s2: string) => s1 === s2 ``` ```js let t2 = "foo" === "foo"; let t3 = { foo: "bar", } === { foo: "bar", }; function doStringsMatch(s1, s2) { return s1 === s2; } let t1 = true; export { t1, t2, t3, doStringsMatch }; ``` ## Deep equality ReScript has the deep equality operator `==` to check deep equality of two items, which is very different from the loose equality operator like `==` in JavaScript. When using `==` in ReScript it will never compile to `==` in JavaScript, it will either compile to `===`, a runtime call to an internal function that deeply compares the equality, or a `bool` if the equality is known to the compiler. ```res let t1 = 1 == 1 // true let t2 = "foo" == "foo" // true let t3 = { "foo": "bar" } == { "foo": "bar"} // true let doStringsMatch = (s1: string, s2: string) => s1 == s2 ``` ```js import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js"; let t2 = true; let t3 = Primitive_object.equal( { foo: "bar", }, { foo: "bar", }, ); function doStringsMatch(s1, s2) { return s1 === s2; } let t1 = true; export { t1, t2, t3, doStringsMatch }; ``` `==` will compile to `===` (or a `bool` if the compiler can determine equality) when: - Comparing `string`, `char`, `int`, `float`, `bool`, or `unit` - Comparing variants or polymorphic variants that do not have constructor values `==` will compile to a runtime check for deep equality when: - Comparing `array`, `tuple`, `list`, `object`, `record`, or regular expression `Re.t` - Comparing variants or polymorphic variants that have constructor values > When using `==` pay close attention to the JavaScript output if you're not sure what `==` will compile to. ## Comparison ReScript has operators for comparing values that compile to the the same operator in JS, a runtime check using an internal function, or a `bool` if the equality is known to the compiler, | operator | comparison | | -------- | --------------------- | | `>` | greater than | | `>=` | greater than or equal | | `<` | less than | | `<=` | less than or equal | Comparison can be done on any type. An operator will compile to the same operator (or a `bool` if the compiler can determine equality) when: - Comparing `int`, `float`, `string`, `char`, `bool` An operator will compile to a runtime check for deep equality when: - Comparing `array`, `tuple`, `list`, `object`, `record`, or regular expression (`Re.t`) - Comparing variants or polymorphic variants ```res let compareInt = (a: int, b: int) => a > b let t1 = 1 > 10 let compareArray = (a: array, b: array) => a > b let compareOptions = (a: option, b: option) => a < b ``` ```js import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js"; function compareInt(a, b) { return a > b; } let compareArray = Primitive_object.greaterthan; let compareOptions = Primitive_object.lessthan; let t1 = false; export { compareInt, t1, compareArray, compareOptions }; ``` ## Performance of runtime equality checks The runtime equality check ReScript uses is quite fast and should be adequate for almost all use cases. For small objects it can be 2x times faster than alternative deep compare functions such as Lodash's [`_.isEqual`](https://lodash.com/docs/4.17.15#isEqual). For larger objects instead of using `==` you could manually use a faster alternative such as [fast-deep-compare](https://www.npmjs.com/package/fast-deep-equal), or write a custom comparator function. [This repo](https://github.com/jderochervlk/rescript-perf) has benchmarks comparing results of different libraries compared to ReScript's built-in equality function.