Skip to content

Commit 79bf029

Browse files
committed
Fixes #91
1 parent 75d503e commit 79bf029

File tree

1 file changed

+39
-215
lines changed

1 file changed

+39
-215
lines changed

pages/docs/manual/latest/lazy-values.mdx

Lines changed: 39 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -6,282 +6,112 @@ canonical: "/docs/manual/latest/lazy-values"
66

77
# Lazy Value
88

9-
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`:
2210

2311
<CodeTab labels={["ReScript", "JS Output"]}>
2412

2513
```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+
})
3919
```
4020
```js
4121
var Fs = require("fs");
42-
var CamlinternalLazy = require("./stdlib/camlinternalLazy.js");
4322

44-
var getFiles = {
23+
var expensiveFilesRead = {
4524
LAZY_DONE: false,
46-
VAL: function () {
25+
VAL: (function () {
4726
console.log("Reading dir");
4827
return Fs.readdirSync("./pages");
49-
},
28+
})
5029
};
51-
52-
console.log(CamlinternalLazy.force(getFiles));
53-
console.log(CamlinternalLazy.force(getFiles));
5430
```
5531

5632
</CodeTab>
5733

58-
...or you can also wrap an existing function to make it lazy:
59-
60-
<CodeTab labels={["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.
7435

75-
function getFiles(param) {
76-
return Fs.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.
7837

79-
var lazyGetFiles = Lazy.from_fun(getFiles);
80-
```
38+
## Execute The Lazy Computation
8139

82-
</CodeTab>
40+
To actually run the lazy value's computation, use `Lazy.force` from the globally available `Lazy` module:
8341

8442
<CodeTab labels={["ReScript", "JS Output"]}>
8543

8644
```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
9147
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
9550
```
9651
```js
97-
var Fs = require("fs");
98-
var Caml_option = require("./stdlib/caml_option.js");
99-
100-
function doesFileExist(name) {
101-
return Caml_option.undefined_to_opt(
102-
Fs.readdirSync("./pages").find(function (s) {
103-
return name === s;
104-
})
105-
);
106-
}
52+
console.log(CamlinternalLazy.force(expensiveFilesRead));
10753

108-
var lazyDoesFileExist = {
109-
LAZY_DONE: false,
110-
VAL: function () {
111-
return doesFileExist("blog.res");
112-
},
113-
};
54+
console.log(CamlinternalLazy.force(expensiveFilesRead));
11455
```
11556

11657
</CodeTab>
11758

118-
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-
<CodeTab labels={["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.
14260

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).
14462

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:
14664

14765
<CodeTab labels={["ReScript", "JS Output"]}>
14866

14967
```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)
15670
}
15771
```
15872
```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);
17374
```
17475

17576
</CodeTab>
17677

177-
<CodeTab labels={["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:
20279

20380
<CodeTab labels={["ReScript", "JS Output"]}>
20481

20582
```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)
21385
```
21486
```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);
24189
```
24290

24391
</CodeTab>
24492

245-
As you can see, the `lazy` syntax is a really great way for creating and handling lazy computations!
93+
## Exception Handling
24694

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:
25096

25197
<CodeTab labels={["ReScript", "JS Output"]}>
25298

25399
```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)
261102
} catch {
262-
| Not_found => Js.log("No file")
103+
| Not_found => [] // empty array of files
263104
}
264105
```
265106
```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: new Error(),
275-
};
276-
},
277-
};
107+
var result;
278108

279109
try {
280-
CamlinternalLazy.force(readFile);
110+
result = CamlinternalLazy.force(expensiveFilesRead);
281111
} catch (raw_exn) {
282112
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
283113
if (exn.RE_EXN_ID === "Not_found") {
284-
console.log("No file");
114+
result = [];
285115
} else {
286116
throw exn;
287117
}
@@ -290,10 +120,4 @@ try {
290120

291121
</CodeTab>
292122

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

Comments
 (0)