Skip to content

Commit 4d7cc30

Browse files
authoredNov 15, 2023
Merge pull request #750 from rescript-association/uncurried-docs
Remove uncurried chapter and update existing docs to uncurried
2 parents b28de11 + f1b025f commit 4d7cc30

File tree

9 files changed

+94
-261
lines changed

9 files changed

+94
-261
lines changed
 

‎data/sidebar_manual_latest.json

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"import-export",
3232
"attribute",
3333
"unboxed",
34-
"uncurried-mode",
3534
"reserved-keywords"
3635
],
3736
"Advanced Features": [
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
id: "uncurried-decorator"
3+
keywords: ["uncurried", "decorator"]
4+
name: "@@uncurried"
5+
summary: "This is the `@@uncurried` decorator."
6+
category: "decorators"
7+
---
8+
9+
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
10+
11+
```rescript
12+
@@uncurried
13+
```
14+
15+
at the top of a `.res` file.
16+
17+
_Available since ReScript `11.0.0`._
18+
19+
### References
20+
21+
- [Uncurried Mode blogpost](/blog/uncurried-mode)
22+
- [Build System configuration](/docs/manual/latest/build-configuration#uncurried)

‎pages/docs/manual/latest/async-await.mdx

-12
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ canonical: "/docs/manual/latest/async-await"
1818

1919
# Async / Await
2020

21-
**Since 10.1**
22-
2321
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.
2422

2523
## How it looks
@@ -113,16 +111,6 @@ let fetchData: string => promise<string> = async (userId: string): string {
113111

114112
**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).
115113

116-
### `async` uncurried functions
117-
118-
The `async` keyword does also work for uncurried functions.
119-
120-
```res
121-
let fetchData = async (. userId: string): string {
122-
await fetchUserMail(userId)
123-
}
124-
```
125-
126114
### Promises don't auto-collapse in async functions
127115

128116
In JS, nested promises (i.e. `promise<promise<'a>>`) 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.

‎pages/docs/manual/latest/bind-to-js-function.mdx

+6-85
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ It'd be nice if on ReScript's side, we can bind & call `draw` while labeling thi
4444

4545
```res example
4646
@module("MyGame")
47-
external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"
47+
external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw"
4848
49-
draw(~x=10, ~y=20, ~border=true, ())
50-
draw(~x=10, ~y=20, ())
49+
draw(~x=10, ~y=20, ~border=true)
50+
draw(~x=10, ~y=20)
5151
```
5252
```js
5353
var MyGame = require("MyGame");
@@ -60,18 +60,16 @@ MyGame.draw(10, 20, undefined);
6060

6161
We've compiled to the same function, but now the usage is much clearer on the ReScript side thanks to labels!
6262

63-
**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.
64-
6563
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:
6664

6765
<CodeTab labels={["ReScript", "JS Output"]}>
6866

6967
```res example
7068
@module("MyGame")
71-
external draw: (~x: int, ~y: int, ~border: bool=?, unit) => unit = "draw"
69+
external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw"
7270
73-
draw(~x=10, ~y=20, ())
74-
draw(~y=20, ~x=10, ())
71+
draw(~x=10, ~y=20)
72+
draw(~y=20, ~x=10)
7573
```
7674
```js
7775
var MyGame = require("MyGame");
@@ -348,83 +346,6 @@ doSomething("test");
348346

349347
**Note:** It's a pretty niche feature, mostly used to map to polymorphic JS APIs.
350348

351-
352-
## Curry & Uncurry
353-
354-
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.
355-
356-
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:
357-
358-
```res sig
359-
let add: (int, int, int) => int
360-
let addFive: (int, int) => int
361-
let twelve: int
362-
```
363-
364-
(In a dynamic language such as JS, currying would be dangerous, since accidentally forgetting to pass an argument doesn't error at compile time).
365-
366-
### Drawback
367-
368-
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:
369-
370-
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.
371-
372-
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.
373-
374-
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.
375-
376-
\* 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.
377-
378-
ReScript tries to do #1 as much as it can. Even when it bails and uses #2's currying mechanism, it's usually harmless.
379-
380-
**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.
381-
382-
### Solution: Use Guaranteed Uncurrying
383-
384-
[Uncurried function](function.md#uncurried-function) annotation also works on `external`:
385-
386-
<CodeTab labels={["ReScript", "JS Output"]}>
387-
388-
```res example
389-
type timerId
390-
@val external setTimeout: ((. unit) => unit, int) => timerId = "setTimeout"
391-
392-
let id = setTimeout((.) => Js.log("hello"), 1000)
393-
```
394-
```js
395-
var id = setTimeout(function () {
396-
console.log("hello");
397-
}, 1000);
398-
```
399-
400-
</CodeTab>
401-
402-
#### Extra Solution
403-
404-
The above solution is safe, guaranteed, and performant, but sometimes visually a little burdensome. We provide an alternative solution if:
405-
406-
- you're using `external`
407-
- the `external` function takes in an argument that's another function
408-
- you want the user **not** to need to annotate the call sites with the dot
409-
410-
<!-- TODO: is this up-to-date info? -->
411-
412-
Then try `@uncurry`:
413-
414-
<CodeTab labels={["ReScript", "JS Output"]}>
415-
416-
```res example
417-
@send external map: (array<'a>, @uncurry ('a => 'b)) => array<'b> = "map"
418-
map([1, 2, 3], x => x + 1)
419-
```
420-
```js
421-
// Empty output
422-
```
423-
424-
</CodeTab>
425-
426-
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.
427-
428349
## Modeling `this`-based Callbacks
429350

430351
Many JS libraries have callbacks which rely on this (the source), for example:

‎pages/docs/manual/latest/build-configuration.mdx

+26-15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ canonical: "/docs/manual/latest/build-configuration"
77

88
# Configuration
99

10-
`rescript.json` (or `rescript.json` in versions prior ReScript 11) is the single, mandatory build meta file needed for `rescript`.
10+
`rescript.json` (or `bsconfig.json` in versions prior ReScript 11) is the single, mandatory build meta file needed for `rescript`.
1111

1212
**The complete configuration schema is [here](./build-configuration-schema)**. We'll _non-exhaustively_ highlight the important parts in prose below.
1313

@@ -96,19 +96,6 @@ This is useful for working on multiple independent ReScript packages simultaneou
9696

9797
More details can be found on our [external stdlib](./build-external-stdlib) page.
9898

99-
## reason, refmt (old)
100-
101-
`reason` config is enabled by default. To turn on JSX for [ReasonReact](https://reasonml.github.io/reason-react/), specify:
102-
103-
```json
104-
{
105-
"reason": {"react-jsx": 3},
106-
"refmt": 3
107-
}
108-
```
109-
110-
The `refmt` config **should be explicitly specified** as `3`.
111-
11299
## js-post-build
113100

114101
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
152139

153140
## suffix
154141

155-
Either `".js"`, `".mjs"`, `".cjs"` or `".bs.js"`. Currently prefer `bs.js` for now.
142+
**Since 11.0**: The suffix can now be freely chosen. However, we still suggest you stick to the convention and use
143+
one of the following:
144+
- `".js`
145+
- `".mjs"`
146+
- `".cjs"`
147+
- `".res.js"`
148+
- `".res.mjs"`
149+
- `".res.cjs"`
150+
- `".bs.js"`
151+
- `".bs.mjs"`
152+
- `".bs.cjs"`
153+
154+
Currently prefer `.bs.js` for now.
156155

157156
### Design Decisions
158157

@@ -163,6 +162,18 @@ Generating JS files with the `.bs.js` suffix means that, on the JS side, you can
163162
- 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**.
164163
- [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.
165164

165+
## uncurried
166+
167+
**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:
168+
169+
```json
170+
{
171+
"uncurried": false
172+
}
173+
```
174+
175+
More details can be found in the [blogpost about "Uncurried Mode"](/blog/uncurried-mode).
176+
166177
## warnings
167178

168179
Selectively turn on/off certain warnings and/or turn them into hard errors. Example:

‎pages/docs/manual/latest/function.mdx

+36-15
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Labeled function arguments can be made optional during declaration. You can then
180180

181181
```res
182182
// radius can be omitted
183-
let drawCircle = (~color, ~radius=?, ()) => {
183+
let drawCircle = (~color, ~radius=?) => {
184184
setColor(color)
185185
switch radius {
186186
| None => startAt(1, 1)
@@ -191,7 +191,7 @@ let drawCircle = (~color, ~radius=?, ()) => {
191191
```js
192192
var Caml_option = require("./stdlib/caml_option.js");
193193

194-
function drawCircle(color, radius, param) {
194+
function drawCircle(color, radius) {
195195
setColor(color);
196196
if (radius === undefined) {
197197
return startAt(1, 1);
@@ -207,8 +207,6 @@ When given in this syntax, `radius` is **wrapped** in the standard library's `op
207207

208208
More on `option` type [here](null-undefined-option.md).
209209

210-
**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.
211-
212210
### Signatures and Type Annotations
213211

214212
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<int>`). 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
218216
<CodeTab labels={["ReScript", "JS Output"]}>
219217

220218
```res
221-
let drawCircle: (~color: color, ~radius: int=?, unit) => unit =
222-
(~color: color, ~radius: option<int>=?, ()) => {
219+
let drawCircle: (~color: color, ~radius: int=?) => unit =
220+
(~color: color, ~radius: option<int>=?) => {
223221
setColor(color)
224222
switch radius {
225223
| None => startAt(1, 1)
@@ -228,7 +226,7 @@ let drawCircle: (~color: color, ~radius: int=?, unit) => unit =
228226
}
229227
```
230228
```js
231-
function drawCircle(color, radius, param) {
229+
function drawCircle(color, radius) {
232230
setColor(color);
233231
if (radius !== undefined) {
234232
return startAt(radius, radius);
@@ -255,16 +253,16 @@ Sometimes, you might want to forward a value to a function without knowing wheth
255253
```res
256254
let result =
257255
switch payloadRadius {
258-
| None => drawCircle(~color, ())
259-
| Some(r) => drawCircle(~color, ~radius=r, ())
256+
| None => drawCircle(~color)
257+
| Some(r) => drawCircle(~color, ~radius=r)
260258
}
261259
```
262260
```js
263261
var r = payloadRadius;
264262

265263
var result = r !== undefined
266-
? drawCircle(color, Caml_option.valFromOption(r), undefined)
267-
: drawCircle(color, undefined);
264+
? drawCircle(color, Caml_option.valFromOption(r))
265+
: drawCircle(color);
268266
```
269267

270268
</CodeTab>
@@ -274,10 +272,10 @@ This quickly gets tedious. We provide a shortcut:
274272
<CodeTab labels={["ReScript", "JS Output"]}>
275273

276274
```res
277-
let result = drawCircle(~color, ~radius=?payloadRadius, ())
275+
let result = drawCircle(~color, ~radius=?payloadRadius)
278276
```
279277
```js
280-
var result = drawCircle(1, undefined, undefined);
278+
var result = drawCircle(1, undefined);
281279
```
282280

283281
</CodeTab>
@@ -291,13 +289,13 @@ Optional labeled arguments can also be provided a default value. In this case, t
291289
<CodeTab labels={["ReScript", "JS Output"]}>
292290

293291
```res
294-
let drawCircle = (~radius=1, ~color, ()) => {
292+
let drawCircle = (~radius=1, ~color) => {
295293
setColor(color)
296294
startAt(radius, radius)
297295
}
298296
```
299297
```js
300-
function drawCircle(radiusOpt, color, param) {
298+
function drawCircle(radiusOpt, color) {
301299
var radius = radiusOpt !== undefined ? radiusOpt : 1;
302300
setColor(color);
303301
return startAt(radius, radius);
@@ -388,6 +386,29 @@ function callFirst(_param) {
388386

389387
</CodeTab>
390388

389+
## Partial Application
390+
391+
**Since 11.0**
392+
393+
To partially apply a function, use the explicit `...` syntax.
394+
395+
<CodeTab labels={["ReScript", "JS Output"]}>
396+
```res
397+
let add = (a, b) => a + b
398+
let addFive = add(5, ...)
399+
```
400+
401+
```js
402+
function add(a, b) {
403+
return a + b | 0;
404+
}
405+
406+
function addFive(extra) {
407+
return 5 + extra | 0;
408+
}
409+
```
410+
</CodeTab>
411+
391412
## Async/Await
392413

393414
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.

‎pages/docs/manual/latest/jsx.mdx

+3-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ React.createElement(MyComponent, {
165165
- Props spread is supported, but there are some restrictions (see below).
166166
- Punning!
167167

168-
### Spread Props (from v10.1)
168+
### Spread Props
169+
170+
**Since 10.1**
169171

170172
JSX props spread is supported now, but in a stricter way than in JS.
171173

‎pages/docs/manual/latest/pipe.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ Let's say you have a function `namePerson`, which takes a `person` then a `name`
196196
<CodeTab labels={["ReScript", "JS Output"]}>
197197

198198
```res
199-
makePerson(~age=47, ())
199+
makePerson(~age=47)
200200
->namePerson("Jane")
201201
```
202202
```js

‎pages/docs/manual/latest/uncurried-mode.mdx

-131
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.