From e1187472c72afb24ae72f234e21eb1bd760cc7e5 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 14 Nov 2023 23:34:23 +0100 Subject: [PATCH 1/3] Remove uncurried chapter and update existing docs to uncurried --- data/sidebar_manual_latest.json | 1 - misc_docs/syntax/decorator_uncurried.mdx | 17 +++ pages/docs/manual/latest/async-await.mdx | 12 -- .../manual/latest/bind-to-js-function.mdx | 91 +----------- .../manual/latest/build-configuration.mdx | 39 ++++-- pages/docs/manual/latest/function.mdx | 49 +++++-- pages/docs/manual/latest/jsx.mdx | 2 +- pages/docs/manual/latest/pipe.mdx | 2 +- pages/docs/manual/latest/promise.mdx | 2 - pages/docs/manual/latest/uncurried-mode.mdx | 131 ------------------ 10 files changed, 83 insertions(+), 263 deletions(-) create mode 100644 misc_docs/syntax/decorator_uncurried.mdx delete mode 100644 pages/docs/manual/latest/uncurried-mode.mdx diff --git a/data/sidebar_manual_latest.json b/data/sidebar_manual_latest.json index 6cdeca5d8..a5fc69ea3 100644 --- a/data/sidebar_manual_latest.json +++ b/data/sidebar_manual_latest.json @@ -31,7 +31,6 @@ "import-export", "attribute", "unboxed", - "uncurried-mode", "reserved-keywords" ], "Advanced Features": [ diff --git a/misc_docs/syntax/decorator_uncurried.mdx b/misc_docs/syntax/decorator_uncurried.mdx new file mode 100644 index 000000000..d6ce943d9 --- /dev/null +++ b/misc_docs/syntax/decorator_uncurried.mdx @@ -0,0 +1,17 @@ +--- +id: "uncurried-decorator" +keywords: ["uncurried", "decorator"] +name: "@@uncurried" +summary: "This is the `@@uncurried` decorator." +category: "decorators" +--- + +If you have uncurried mode turned off in `rescript.json` and still want to try it on a per-file basis, you can turn it on via + +```rescript +@@uncurried +``` + +at the top of a `.res` file. + +_Available since ReScript `11.0.0`._ \ No newline at end of file diff --git a/pages/docs/manual/latest/async-await.mdx b/pages/docs/manual/latest/async-await.mdx index d5ae72fde..da79359df 100644 --- a/pages/docs/manual/latest/async-await.mdx +++ b/pages/docs/manual/latest/async-await.mdx @@ -18,8 +18,6 @@ canonical: "/docs/manual/latest/async-await" # Async / Await -**Since 10.1** - 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 @@ -113,16 +111,6 @@ let fetchData: string => promise = async (userId: string): string { **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). -### `async` uncurried functions - -The `async` keyword does also work for uncurried functions. - -```res -let fetchData = async (. userId: string): string { - await fetchUserMail(userId) -} -``` - ### 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. diff --git a/pages/docs/manual/latest/bind-to-js-function.mdx b/pages/docs/manual/latest/bind-to-js-function.mdx index 415000bc3..8ade03ce2 100644 --- a/pages/docs/manual/latest/bind-to-js-function.mdx +++ b/pages/docs/manual/latest/bind-to-js-function.mdx @@ -44,10 +44,10 @@ It'd be nice if on ReScript's side, we can bind & call `draw` while labeling thi ```res example @module("MyGame") -external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw" +external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw" -draw(~x=10, ~y=20, ~border=true, ()) -draw(~x=10, ~y=20, ()) +draw(~x=10, ~y=20, ~border=true) +draw(~x=10, ~y=20) ``` ```js var MyGame = require("MyGame"); @@ -60,18 +60,16 @@ MyGame.draw(10, 20, undefined); We've compiled to the same function, but now the usage is much clearer on the ReScript side thanks to labels! -**Note**: in this particular case, you need a unit, `()` after `border`, since `border` is an [optional argument at the last position](function.md#optional-labeled-arguments). Not having a unit to indicate you've finished applying the function would generate a warning. - Note that you can freely reorder the labels on the ReScript side; they'll always correctly appear in their declaration order in the JavaScript output: ```res example @module("MyGame") -external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw" +external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw" -draw(~x=10, ~y=20, ()) -draw(~y=20, ~x=10, ()) +draw(~x=10, ~y=20) +draw(~y=20, ~x=10) ``` ```js var MyGame = require("MyGame"); @@ -348,83 +346,6 @@ doSomething("test"); **Note:** It's a pretty niche feature, mostly used to map to polymorphic JS APIs. - -## Curry & Uncurry - -Curry is a delicious Indian dish. More importantly, in the context of ReScript (and functional programming in general), currying means that function taking multiple arguments can be applied a few arguments at time, until all the arguments are applied. - -See the `addFive` intermediate function? `add` takes in 3 arguments but received only 1. It's interpreted as "currying" the argument `5` and waiting for the next 2 arguments to be applied later on. Type signatures: - -```res sig -let add: (int, int, int) => int -let addFive: (int, int) => int -let twelve: int -``` - -(In a dynamic language such as JS, currying would be dangerous, since accidentally forgetting to pass an argument doesn't error at compile time). - -### Drawback - -Unfortunately, due to JS not having currying because of the aforementioned reason, it's hard for ReScript multi-argument functions to map cleanly to JS functions 100% of the time: - -1. When all the arguments of a function are supplied (aka no currying), ReScript does its best to to compile e.g. a 3-arguments call into a plain JS call with 3 arguments. - -2. If it's too hard to detect whether a function application is complete\*, ReScript will use a runtime mechanism (the `Curry` module) to curry as many args as we can and check whether the result is fully applied. - -3. Some JS APIs like `throttle`, `debounce` and `promise` might mess with context, aka use the function `bind` mechanism, carry around `this`, etc. Such implementation clashes with the previous currying logic. - -\* If the call site is typed as having 3 arguments, we sometimes don't know whether it's a function that's being curried, or if the original one indeed has only 3 arguments. - -ReScript tries to do #1 as much as it can. Even when it bails and uses #2's currying mechanism, it's usually harmless. - -**However**, if you encounter #3, heuristics are not good enough: you need a guaranteed way of fully applying a function, without intermediate currying steps. We provide such guarantee through the use of the ["uncurrying" syntax](./function#uncurried-function) on a function declaration & call site. - -### Solution: Use Guaranteed Uncurrying - -[Uncurried function](function.md#uncurried-function) annotation also works on `external`: - - - -```res example -type timerId -@val external setTimeout: ((. unit) => unit, int) => timerId = "setTimeout" - -let id = setTimeout((.) => Js.log("hello"), 1000) -``` -```js -var id = setTimeout(function () { - console.log("hello"); -}, 1000); -``` - - - -#### Extra Solution - -The above solution is safe, guaranteed, and performant, but sometimes visually a little burdensome. We provide an alternative solution if: - -- you're using `external` -- the `external` function takes in an argument that's another function -- you want the user **not** to need to annotate the call sites with the dot - - - -Then try `@uncurry`: - - - -```res example -@send external map: (array<'a>, @uncurry ('a => 'b)) => array<'b> = "map" -map([1, 2, 3], x => x + 1) -``` -```js -// Empty output -``` - - - -In general, `uncurry` is recommended; the compiler will do lots of optimizations to resolve the currying to uncurrying at compile time. However, there are some cases the compiler can't optimize it. In these cases, it will be converted to a runtime check. - ## Modeling `this`-based Callbacks Many JS libraries have callbacks which rely on this (the source), for example: diff --git a/pages/docs/manual/latest/build-configuration.mdx b/pages/docs/manual/latest/build-configuration.mdx index ab045f030..3e8d08a29 100644 --- a/pages/docs/manual/latest/build-configuration.mdx +++ b/pages/docs/manual/latest/build-configuration.mdx @@ -7,7 +7,7 @@ canonical: "/docs/manual/latest/build-configuration" # Configuration -`rescript.json` (or `rescript.json` in versions prior ReScript 11) is the single, mandatory build meta file needed for `rescript`. +`rescript.json` (or `bsconfig.json` in versions prior ReScript 11) is the single, mandatory build meta file needed for `rescript`. **The complete configuration schema is [here](./build-configuration-schema)**. We'll _non-exhaustively_ highlight the important parts in prose below. @@ -96,19 +96,6 @@ This is useful for working on multiple independent ReScript packages simultaneou More details can be found on our [external stdlib](./build-external-stdlib) page. -## reason, refmt (old) - -`reason` config is enabled by default. To turn on JSX for [ReasonReact](https://reasonml.github.io/reason-react/), specify: - -```json -{ - "reason": {"react-jsx": 3}, - "refmt": 3 -} -``` - -The `refmt` config **should be explicitly specified** as `3`. - ## js-post-build Hook that's invoked every time a file is recompiled. Good for JS build system interop, but please use it **sparingly**. Calling your custom command for every recompiled file slows down your build and worsens the building experience for even third-party users of your lib. @@ -152,7 +139,19 @@ This configuration only applies to you, when you develop the project. When the p ## suffix -Either `".js"`, `".mjs"`, `".cjs"` or `".bs.js"`. Currently prefer `bs.js` for now. +**Since 11.0**: The suffix can now be freely chosen. However, we still suggest you stick to the convention and use +one of the following: +- `".js` +- `".mjs"` +- `".cjs"` +- `".res.js"` +- `".res.mjs"` +- `".res.cjs"` +- `".bs.js"` +- `".bs.mjs"` +- `".bs.cjs"` + +Currently prefer `.bs.js` for now. ### Design Decisions @@ -163,6 +162,16 @@ Generating JS files with the `.bs.js` suffix means that, on the JS side, you can - It avoids the need of using a build system loader for ReScript files. This + in-source build means integrating a ReScript project into your pure JS codebase **basically doesn't touch anything in your build pipeline at all**. - [genType](/docs/gentype/latest/introduction) requires `bs.js` for compiled JS artifacts. If you are using `genType`, you need to use `bs.js` for now. +## uncurried + +**Since 11.0**: While we strongly encourage all users to use uncurried mode, it is still possible to opt out. Just set `"uncurried"` to `false` to get the old behavior back: + +```json +{ + "uncurried": false +} +``` + ## warnings Selectively turn on/off certain warnings and/or turn them into hard errors. Example: diff --git a/pages/docs/manual/latest/function.mdx b/pages/docs/manual/latest/function.mdx index c2dae821f..a22b3ef9a 100644 --- a/pages/docs/manual/latest/function.mdx +++ b/pages/docs/manual/latest/function.mdx @@ -180,7 +180,7 @@ Labeled function arguments can be made optional during declaration. You can then ```res // radius can be omitted -let drawCircle = (~color, ~radius=?, ()) => { +let drawCircle = (~color, ~radius=?) => { setColor(color) switch radius { | None => startAt(1, 1) @@ -191,7 +191,7 @@ let drawCircle = (~color, ~radius=?, ()) => { ```js var Caml_option = require("./stdlib/caml_option.js"); -function drawCircle(color, radius, param) { +function drawCircle(color, radius) { setColor(color); if (radius === undefined) { return startAt(1, 1); @@ -207,8 +207,6 @@ When given in this syntax, `radius` is **wrapped** in the standard library's `op More on `option` type [here](null-undefined-option.md). -**Note** for the sake of the type system, whenever you have an optional argument, you need to ensure that there's also at least one positional argument (aka non-labeled, non-optional argument) after it. If there's none, provide a dummy `unit` (aka `()`) argument. - ### 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. @@ -218,8 +216,8 @@ If we get back to our previous example and both add a signature and type annotat ```res -let drawCircle: (~color: color, ~radius: int=?, unit) => unit = - (~color: color, ~radius: option=?, ()) => { +let drawCircle: (~color: color, ~radius: int=?) => unit = + (~color: color, ~radius: option=?) => { setColor(color) switch radius { | None => startAt(1, 1) @@ -228,7 +226,7 @@ let drawCircle: (~color: color, ~radius: int=?, unit) => unit = } ``` ```js -function drawCircle(color, radius, param) { +function drawCircle(color, radius) { setColor(color); if (radius !== undefined) { return startAt(radius, radius); @@ -255,16 +253,16 @@ Sometimes, you might want to forward a value to a function without knowing wheth ```res let result = switch payloadRadius { - | None => drawCircle(~color, ()) - | Some(r) => drawCircle(~color, ~radius=r, ()) + | None => drawCircle(~color) + | Some(r) => drawCircle(~color, ~radius=r) } ``` ```js var r = payloadRadius; var result = r !== undefined - ? drawCircle(color, Caml_option.valFromOption(r), undefined) - : drawCircle(color, undefined); + ? drawCircle(color, Caml_option.valFromOption(r)) + : drawCircle(color); ``` @@ -274,10 +272,10 @@ This quickly gets tedious. We provide a shortcut: ```res -let result = drawCircle(~color, ~radius=?payloadRadius, ()) +let result = drawCircle(~color, ~radius=?payloadRadius) ``` ```js -var result = drawCircle(1, undefined, undefined); +var result = drawCircle(1, undefined); ``` @@ -291,13 +289,13 @@ Optional labeled arguments can also be provided a default value. In this case, t ```res -let drawCircle = (~radius=1, ~color, ()) => { +let drawCircle = (~radius=1, ~color) => { setColor(color) startAt(radius, radius) } ``` ```js -function drawCircle(radiusOpt, color, param) { +function drawCircle(radiusOpt, color) { var radius = radiusOpt !== undefined ? radiusOpt : 1; setColor(color); return startAt(radius, radius); @@ -388,6 +386,27 @@ function callFirst(_param) { +## Curried Function + +ReScript's functions are uncurried by default since version 11. If you need to partially apply a function, there is now explicit syntax for it: + + +```res +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. diff --git a/pages/docs/manual/latest/jsx.mdx b/pages/docs/manual/latest/jsx.mdx index 178e950ab..cefaca94c 100644 --- a/pages/docs/manual/latest/jsx.mdx +++ b/pages/docs/manual/latest/jsx.mdx @@ -165,7 +165,7 @@ React.createElement(MyComponent, { - Props spread is supported, but there are some restrictions (see below). - Punning! -### Spread Props (from v10.1) +### Spread Props JSX props spread is supported now, but in a stricter way than in JS. diff --git a/pages/docs/manual/latest/pipe.mdx b/pages/docs/manual/latest/pipe.mdx index 776c268bb..4d5e24ecf 100644 --- a/pages/docs/manual/latest/pipe.mdx +++ b/pages/docs/manual/latest/pipe.mdx @@ -196,7 +196,7 @@ Let's say you have a function `namePerson`, which takes a `person` then a `name` ```res -makePerson(~age=47, ()) +makePerson(~age=47) ->namePerson("Jane") ``` ```js diff --git a/pages/docs/manual/latest/promise.mdx b/pages/docs/manual/latest/promise.mdx index 1027b9bfd..23cf909e2 100644 --- a/pages/docs/manual/latest/promise.mdx +++ b/pages/docs/manual/latest/promise.mdx @@ -10,8 +10,6 @@ canonical: "/docs/manual/latest/promise" ## `promise` type -**Since 10.1** - In ReScript, every JS promise is represented with the globally available `promise<'a>` type. For ReScript versions < 10.1, use its original alias `Js.Promise.t<'a>` instead. Here's a usage example in a function signature: diff --git a/pages/docs/manual/latest/uncurried-mode.mdx b/pages/docs/manual/latest/uncurried-mode.mdx deleted file mode 100644 index c139f3c24..000000000 --- a/pages/docs/manual/latest/uncurried-mode.mdx +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: "Uncurried Mode" -description: "Uncurried Mode" -canonical: "/docs/manual/latest/uncurried-mode" ---- - -# Uncurried Mode - -Since ReScript 11, the language compiles in "uncurried" mode by default. -Before that, currying in ReScript looked like this: - - - -```res -let add = (a, b) => a + b -let addFive = add(5) -``` - -```js -function add(a, b) { - return a + b | 0; -} - -function addFive(param) { - return 5 + param | 0; -} -``` - - - -It is shorter than having to write all remaining parameters again. - - - -```res -let add = (a, b) => a + b -let addFive = (b) => add(5, b) -``` - -```js -function add(a, b) { - return a + b | 0; -} - -function addFive(b) { - return 5 + b | 0; -} -``` - - - -In ReScript 11, that would yield an error. - -```rescript -let add = (a, b) => a + b -let addFive = add(5) // <-- Error: -// This uncurried function has type (. int, int) => int -// It is applied with 1 arguments but it requires 2. -``` - -To fix it, you can just state the remaining parameters explicitly. - - -```res -let add = (a, b) => a + b -let addFive = (b) => add(5, b) -``` - -```js -function add(a, b) { - return a + b | 0; -} - -function addFive(b) { - return 5 + b | 0; -} -``` - - -Or use the new explicit syntax for partial application. - - -```res -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; -} -``` - - -The former approach helps library authors support both ReScript 11 and earlier versions. - -### No final unit necessary - -In Uncurried Mode, the "final unit" pattern is not necessary anymore, while optional or default parameters are still supported. - -```res -// old -let myFun = (~name=?, ()) - -// new -let myFun = (~name=?) -``` - -### How to switch back to curried mode - -While we strongly encourage all users to switch to the new uncurried mode, it is still possible to opt out. Just add a - -```json -{ - "uncurried": false -} -``` - -to your `rescript.json`, and your project will be compiled in curried mode again. - -If you have uncurried mode off and still want to try it on a per-file basis, you can turn it on via - -```rescript -@@uncurried -``` - -at the top of a `.res` file. \ No newline at end of file From 76713c7d40f630d1cc27ae78aa79ead04a930e4b Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 15 Nov 2023 09:16:37 +0100 Subject: [PATCH 2/3] Add references to @@uncurried in syntax lookup --- misc_docs/syntax/decorator_uncurried.mdx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/misc_docs/syntax/decorator_uncurried.mdx b/misc_docs/syntax/decorator_uncurried.mdx index d6ce943d9..838de33e6 100644 --- a/misc_docs/syntax/decorator_uncurried.mdx +++ b/misc_docs/syntax/decorator_uncurried.mdx @@ -14,4 +14,9 @@ If you have uncurried mode turned off in `rescript.json` and still want to try i at the top of a `.res` file. -_Available since ReScript `11.0.0`._ \ No newline at end of file +_Available since ReScript `11.0.0`._ + +### References + +- [Uncurried Mode blogpost](blog/uncurried-mode) +- [Build System configuration](docs/manual/latest/build-configuration#uncurried) From f1b025f94fc9a5358d8b43ef011c56f3dc25f02d Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 15 Nov 2023 09:30:32 +0100 Subject: [PATCH 3/3] Fixes according comments --- misc_docs/syntax/decorator_uncurried.mdx | 4 ++-- pages/docs/manual/latest/build-configuration.mdx | 2 ++ pages/docs/manual/latest/function.mdx | 6 ++++-- pages/docs/manual/latest/jsx.mdx | 2 ++ pages/docs/manual/latest/promise.mdx | 2 ++ 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/misc_docs/syntax/decorator_uncurried.mdx b/misc_docs/syntax/decorator_uncurried.mdx index 838de33e6..a0d7ba83e 100644 --- a/misc_docs/syntax/decorator_uncurried.mdx +++ b/misc_docs/syntax/decorator_uncurried.mdx @@ -18,5 +18,5 @@ _Available since ReScript `11.0.0`._ ### References -- [Uncurried Mode blogpost](blog/uncurried-mode) -- [Build System configuration](docs/manual/latest/build-configuration#uncurried) +- [Uncurried Mode blogpost](/blog/uncurried-mode) +- [Build System configuration](/docs/manual/latest/build-configuration#uncurried) diff --git a/pages/docs/manual/latest/build-configuration.mdx b/pages/docs/manual/latest/build-configuration.mdx index 3e8d08a29..0dc8fe5b8 100644 --- a/pages/docs/manual/latest/build-configuration.mdx +++ b/pages/docs/manual/latest/build-configuration.mdx @@ -172,6 +172,8 @@ Generating JS files with the `.bs.js` suffix means that, on the JS side, you can } ``` +More details can be found in the [blogpost about "Uncurried Mode"](/blog/uncurried-mode). + ## warnings Selectively turn on/off certain warnings and/or turn them into hard errors. Example: diff --git a/pages/docs/manual/latest/function.mdx b/pages/docs/manual/latest/function.mdx index a22b3ef9a..3ec35d0ad 100644 --- a/pages/docs/manual/latest/function.mdx +++ b/pages/docs/manual/latest/function.mdx @@ -386,9 +386,11 @@ function callFirst(_param) { -## Curried Function +## Partial Application -ReScript's functions are uncurried by default since version 11. If you need to partially apply a function, there is now explicit syntax for it: +**Since 11.0** + +To partially apply a function, use the explicit `...` syntax. ```res diff --git a/pages/docs/manual/latest/jsx.mdx b/pages/docs/manual/latest/jsx.mdx index cefaca94c..e4320a515 100644 --- a/pages/docs/manual/latest/jsx.mdx +++ b/pages/docs/manual/latest/jsx.mdx @@ -167,6 +167,8 @@ React.createElement(MyComponent, { ### Spread Props +**Since 10.1** + JSX props spread is supported now, but in a stricter way than in JS. diff --git a/pages/docs/manual/latest/promise.mdx b/pages/docs/manual/latest/promise.mdx index 23cf909e2..1027b9bfd 100644 --- a/pages/docs/manual/latest/promise.mdx +++ b/pages/docs/manual/latest/promise.mdx @@ -10,6 +10,8 @@ canonical: "/docs/manual/latest/promise" ## `promise` type +**Since 10.1** + In ReScript, every JS promise is represented with the globally available `promise<'a>` type. For ReScript versions < 10.1, use its original alias `Js.Promise.t<'a>` instead. Here's a usage example in a function signature: