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
A lazy value represents a deferred computation which will automatically memoize the result on the first run, and then return the memoized result on any repeated execution.
10
-
11
-
This is useful for defining functions and expressions for complex procedures that always return the same value, for example:
12
-
13
-
- Doing expensive DOM traversals over the same tree over and over again
14
-
- Doing file system operations on a static set of files that won't change
15
-
- Doing expensive requests to an API server that would always return the same data
16
-
17
-
A lazy value has a type of `Lazy.t('a)`, where `'a` is the return value of the computation. All its functionality is encapsulated with the globally available `Lazy` module.
18
-
19
-
## Creating a lazy value
20
-
21
-
Lazy values are part of the language. You can either use the `lazy` keyword to create a lazy value from an expression...
9
+
If you have some expensive computations you'd like to **defer and cache** subsequently, you can wrap it with `lazy`:
22
10
23
11
<CodeTablabels={["ReScript", "JS Output"]}>
24
12
25
13
```res example
26
-
// We only want getFiles to read the file system once,
27
-
// so we wrap it in a lazy value
28
-
let getFiles =
29
-
lazy({
30
-
Js.log("Reading dir")
31
-
Node.Fs.readdirSync("./pages")
32
-
})
33
-
34
-
// On the first call, the computation takes place
35
-
Lazy.force(getFiles)->Js.log
36
-
37
-
// The second call will just return the already calculated files
38
-
Lazy.force(getFiles)->Js.log
14
+
// Read the directory, only once
15
+
let expensiveFilesRead = lazy({
16
+
Js.log("Reading dir")
17
+
Node.Fs.readdirSync("./pages")
18
+
})
39
19
```
40
20
```js
41
21
var Fs =require("fs");
42
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
43
22
44
-
vargetFiles= {
23
+
varexpensiveFilesRead= {
45
24
LAZY_DONE:false,
46
-
VAL:function () {
25
+
VAL:(function () {
47
26
console.log("Reading dir");
48
27
returnFs.readdirSync("./pages");
49
-
},
28
+
})
50
29
};
51
-
52
-
console.log(CamlinternalLazy.force(getFiles));
53
-
console.log(CamlinternalLazy.force(getFiles));
54
30
```
55
31
56
32
</CodeTab>
57
33
58
-
...or you can also wrap an existing function to make it lazy:
59
-
60
-
<CodeTablabels={["ReScript", "JS Output"]}>
61
-
62
-
```res example
63
-
// Example for wrapping a function with 0 parameters
64
-
let getFiles = () => {
65
-
Node.Fs.readdirSync("./pages")
66
-
}
67
-
68
-
// Here we wrap our function in the lazy value
69
-
let lazyGetFiles = Lazy.from_fun(getFiles)
70
-
```
71
-
```js
72
-
var Fs =require("fs");
73
-
var Lazy =require("./stdlib/lazy.js");
34
+
Check the JS Output tab: that `expensiveFilesRead`'s code isn't executed yet, even though you declared it! You can carry it around without fearing that it'll run the directory read.
74
35
75
-
functiongetFiles(param) {
76
-
returnFs.readdirSync("./pages");
77
-
}
36
+
**Note**: a lazy value is **not** a [shared data type](shared-data-types.md). Don't rely on its runtime representation in your JavaScript code.
78
37
79
-
var lazyGetFiles =Lazy.from_fun(getFiles);
80
-
```
38
+
## Execute The Lazy Computation
81
39
82
-
</CodeTab>
40
+
To actually run the lazy value's computation, use `Lazy.force` from the globally available `Lazy` module:
83
41
84
42
<CodeTablabels={["ReScript", "JS Output"]}>
85
43
86
44
```res example
87
-
// Example for wrapping a function with parameters
88
-
let doesFileExist = name => {
89
-
Node.Fs.readdirSync("./pages")->Js.Array2.find(s => name === s)
90
-
}
45
+
// First call. The computation happens
46
+
Js.log(Lazy.force(expensiveFilesRead)) // logs "Reading dir" and the directory content
91
47
92
-
// Here we use the lazy syntax again
93
-
// since we can't use Lazy.from_fun
94
-
let lazyDoesFileExist = lazy(doesFileExist("blog.res"))
48
+
// Second call. Will just return the already calculated result
49
+
Js.log(Lazy.force(expensiveFilesRead)) // logs the directory content
95
50
```
96
51
```js
97
-
var Fs =require("fs");
98
-
var Caml_option =require("./stdlib/caml_option.js");
Whenever we want to wrap a function `unit => 'a`, we use `Lazy.from_fun`, otherwise we use the `lazy(expr)` keyword to wrap an expression or a function with 1 or more arguments.
119
-
120
-
## Force a lazy computation
121
-
122
-
Lazy values need to be explicitly executed to be able to return a value. Use the `Lazy.force`to start the execution:
123
-
124
-
<CodeTablabels={["ReScript", "JS Output"]}>
125
-
126
-
```res example
127
-
let computation = lazy(1)
128
-
129
-
// Returns 1
130
-
Lazy.force(computation)
131
-
```
132
-
```js
133
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
134
-
135
-
var computation = {
136
-
LAZY_DONE:true,
137
-
VAL:1,
138
-
};
139
-
140
-
CamlinternalLazy.force(computation);
141
-
```
59
+
The first time `Lazy.force` is called, the expensive computation happens and the result is **cached**. The second time, the cached value is directly used.
142
60
143
-
</CodeTab>
61
+
**You can't re-trigger the computation after the first `force` 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).
144
62
145
-
It is also possible to use pattern matching to force a lazy value to compute, this includes `switch` expressions and similar syntax such as tuple destructuring:
63
+
Instead of using `Lazy.force`, you can also use [pattern matching](/pattern-matching-destructuring.mdx) to trigger the computation:
146
64
147
65
<CodeTablabels={["ReScript", "JS Output"]}>
148
66
149
67
```res example
150
-
// Extract a lazy value via pattern matching
151
-
let computation = lazy("computed")
152
-
153
-
switch computation {
154
-
| lazy("computed") => Js.log("ok")
155
-
| _ => Js.log("not ok")
68
+
switch expensiveFilesRead {
69
+
| lazy(result) => Js.log(result)
156
70
}
157
71
```
158
72
```js
159
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
160
-
161
-
var computation = {
162
-
LAZY_DONE:true,
163
-
VAL:"computed",
164
-
};
165
-
166
-
var match =CamlinternalLazy.force(computation);
167
-
168
-
if (match ==="computed") {
169
-
console.log("ok");
170
-
} else {
171
-
console.log("not ok");
172
-
}
73
+
var result =CamlinternalLazy.force(expensiveFilesRead);
173
74
```
174
75
175
76
</CodeTab>
176
77
177
-
<CodeTablabels={["ReScript", "JS Output"]}>
178
-
179
-
```res example
180
-
// Destructuring a single value
181
-
// Note: currently the formatter will reprint this
182
-
// as `let lazy word = ...`
183
-
let lazy(word) = lazy("hello")
184
-
185
-
// Output: "hello"
186
-
Js.log(word)
187
-
```
188
-
```js
189
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
190
-
191
-
var match = {
192
-
LAZY_DONE:true,
193
-
VAL:"hello",
194
-
};
195
-
196
-
var word =CamlinternalLazy.force(match);
197
-
198
-
console.log(word);
199
-
```
200
-
201
-
</CodeTab>
78
+
Since pattern matching also works on a `let` binding, you can also do:
202
79
203
80
<CodeTablabels={["ReScript", "JS Output"]}>
204
81
205
82
```res example
206
-
// Destructing a tuple
207
-
let lazyValues = (lazy("hello"), lazy("world"))
208
-
let (lazy(word1), lazy(word2)) = lazyValues
209
-
210
-
// Output: "hello world"
211
-
Js.log2(word1, word2)
212
-
let lazy(word) = lazy("hello")
83
+
let lazy(result) = expensiveFilesRead
84
+
Js.log(result)
213
85
```
214
86
```js
215
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
216
-
217
-
var lazyValues_0 = {
218
-
LAZY_DONE:true,
219
-
VAL:"hello",
220
-
};
221
-
222
-
var lazyValues_1 = {
223
-
LAZY_DONE:true,
224
-
VAL:"world",
225
-
};
226
-
227
-
var lazyValues = [lazyValues_0, lazyValues_1];
228
-
229
-
var word1 =CamlinternalLazy.force(lazyValues_0);
230
-
231
-
var word2 =CamlinternalLazy.force(lazyValues_1);
232
-
233
-
console.log(word1, word2);
234
-
235
-
var match = {
236
-
LAZY_DONE:true,
237
-
VAL:"hello",
238
-
};
239
-
240
-
var word =CamlinternalLazy.force(match);
87
+
var result =CamlinternalLazy.force(expensiveFilesRead);
88
+
console.log(result);
241
89
```
242
90
243
91
</CodeTab>
244
92
245
-
As you can see, the `lazy` syntax is a really great way for creating and handling lazy computations!
93
+
## Exception Handling
246
94
247
-
## Exception handling
248
-
249
-
Whenever a lazy value computation raises an exception, the same exception will be thrown by `Lazy.force`.
95
+
For completeness' sake, our files read example might raise an exception because of `readdirSync`. Here's how you'd handle it:
250
96
251
97
<CodeTablabels={["ReScript", "JS Output"]}>
252
98
253
99
```res example
254
-
let readFile =
255
-
lazy({
256
-
raise(Not_found)
257
-
})
258
-
259
-
try {
260
-
Lazy.force(readFile)
100
+
let result = try {
101
+
Lazy.force(expensiveFilesRead)
261
102
} catch {
262
-
| Not_found => Js.log("No file")
103
+
| Not_found => [] // empty array of files
263
104
}
264
105
```
265
106
```js
266
-
var CamlinternalLazy =require("./stdlib/camlinternalLazy.js");
267
-
var Caml_js_exceptions =require("./stdlib/caml_js_exceptions.js");
268
-
269
-
var readFile = {
270
-
LAZY_DONE:false,
271
-
VAL:function () {
272
-
throw {
273
-
RE_EXN_ID:"Not_found",
274
-
Error:newError(),
275
-
};
276
-
},
277
-
};
107
+
var result;
278
108
279
109
try {
280
-
CamlinternalLazy.force(readFile);
110
+
result =CamlinternalLazy.force(expensiveFilesRead);
281
111
} catch (raw_exn) {
282
112
var exn =Caml_js_exceptions.internalToOCamlException(raw_exn);
283
113
if (exn.RE_EXN_ID==="Not_found") {
284
-
console.log("No file");
114
+
result = [];
285
115
} else {
286
116
throw exn;
287
117
}
@@ -290,10 +120,4 @@ try {
290
120
291
121
</CodeTab>
292
122
293
-
Nothing new here, since we are using the `try` expression to match the exception raised in the lazy computation!
294
-
295
-
Please remember: Exceptions should be used sparsely!
296
-
297
-
## Notes
298
-
299
-
A lazy value is **not** a [shared data type](shared-data-types.md). Don't rely on the runtime representation on the JS side.
123
+
Though you should probably handle the exception inside the lazy computation itself.
0 commit comments