title | description | canonical |
---|---|---|
Lazy Value |
Data type for deferred computation in ReScript |
/docs/manual/v11.0.0/lazy-values |
If you have some expensive computations you'd like to defer and cache subsequently, you can wrap it with lazy
:
<CodeTab labels={["ReScript", "JS Output"]}>
@module("node:fs")
external readdirSync: string => array<string> = "readdirSync"
// Read the directory, only once
let expensiveFilesRead = lazy({
Console.log("Reading dir")
readdirSync("./pages")
})
var Fs = require("fs");
var expensiveFilesRead = {
LAZY_DONE: false,
VAL: (function () {
console.log("Reading dir");
return Fs.readdirSync("./pages");
})
};
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.
Note: a lazy value is not a shared data type. Don't rely on its runtime representation in your JavaScript code.
To actually run the lazy value's computation, use Lazy.force
from the globally available Lazy
module:
<CodeTab labels={["ReScript", "JS Output"]}>
// First call. The computation happens
Console.log(Lazy.force(expensiveFilesRead)) // logs "Reading dir" and the directory content
// Second call. Will just return the already calculated result
Console.log(Lazy.force(expensiveFilesRead)) // logs the directory content
console.log(CamlinternalLazy.force(expensiveFilesRead));
console.log(CamlinternalLazy.force(expensiveFilesRead));
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.
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).
Instead of using Lazy.force
, you can also use pattern matching to trigger the computation:
<CodeTab labels={["ReScript", "JS Output"]}>
switch expensiveFilesRead {
| lazy(result) => Console.log(result)
}
var result = CamlinternalLazy.force(expensiveFilesRead);
Since pattern matching also works on a let
binding, you can also do:
<CodeTab labels={["ReScript", "JS Output"]}>
let lazy(result) = expensiveFilesRead
Console.log(result)
var result = CamlinternalLazy.force(expensiveFilesRead);
console.log(result);
For completeness' sake, our files read example might raise an exception because of readdirSync
. Here's how you'd handle it:
<CodeTab labels={["ReScript", "JS Output"]}>
let result = try {
Lazy.force(expensiveFilesRead)
} catch {
| Not_found => [] // empty array of files
}
var result;
try {
result = CamlinternalLazy.force(expensiveFilesRead);
} catch (raw_exn) {
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === "Not_found") {
result = [];
} else {
throw exn;
}
}
Though you should probably handle the exception inside the lazy computation itself.