You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _blogposts/2025-09-01-let-unwrap.mdx
+62-1Lines changed: 62 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,15 +15,76 @@ After long discussions we finally decided on an unwrap syntax for both the `opti
15
15
16
16
### Example
17
17
18
+
Before showing off this new feauture, let's explore why it is useful. Consider a chain of `async` functions that are dependent on the result of the previous one. The naive way to write this in modern ReScript with `async`/`await` is to just `switch` on the results.
19
+
20
+
```res
21
+
let getUser = async id =>
22
+
switch await fetchUser(id) {
23
+
| Error(error) => Error(error)
24
+
| Ok(res) =>
25
+
switch await decodeUser(res) {
26
+
| Error(error) => Error(error)
27
+
| Ok(decodedUser) =>
28
+
switch await ensureUserActive(decodedUser) {
29
+
| Error(error) => Error(error)
30
+
| Ok() => Ok(decodedUser)
31
+
}
32
+
}
33
+
}
34
+
```
35
+
36
+
Two observations:
37
+
1. with every `switch` expression, this function gets nested deeper.
38
+
2. The `Error` branch of every `switch` is just an identity mapper (neither wrapper nor contents change)
39
+
40
+
This means even though `async`/`await` syntax is available in ReScript for some time now, it is also understandable that people created their own `AsyncResult` libraries to handle such things with less lines of code, e.g.:
41
+
42
+
```res
43
+
let getUser = async id =>
44
+
switch await fetchUser(id) {
45
+
| Error(error) => Error(error)
46
+
| Ok(res) =>
47
+
switch await decodeUser(res) {
48
+
| Error(error) => Error(error)
49
+
| Ok(decodedUser) =>
50
+
switch await ensureUserActive(decodedUser) {
51
+
| Error(error) => Error(error)
52
+
| Ok() => Ok(decodedUser)
53
+
}
54
+
}
55
+
}
56
+
```
57
+
58
+
One way to mitigate this was to use some `ResultPromise` library:
While this is much shorter, it is also harder to understand because we have two wrapper types here, `promise` and `result`. And we have to wrap the non-async type in a `Promise.resolve` in order to stay on the same type level.
76
+
18
77
```rescript
19
78
let getUser = async (id) => {
20
79
let? Ok(user) = await fetchUser(id)
21
80
let? Ok(decodedUser) = decodeUser(user)
22
-
Console.log(`Got user ${decodedUser.name}!`)
23
81
let? Ok() = await ensureUserActive(decodedUser)
24
82
Ok(decodedUser)
25
83
}
26
84
```
85
+
With the new `let-unwrap` syntax, `let?` in short, we now have to follow the happy-path (in the scope of the function). And it's immediately clear that `fetchUser` is an `async` function while `decodeUser` is not. There is no nesting as the `Error` is automatically mapped. But be assured the error case is also handled as the type checker will complain when you don't handle the `Error` returned by the `getUser` function.
0 commit comments