Skip to content

Latest commit

 

History

History
126 lines (93 loc) · 3.29 KB

lazy-values.mdx

File metadata and controls

126 lines (93 loc) · 3.29 KB
title description canonical
Lazy Value
Data type for deferred computation in ReScript
/docs/manual/v11.0.0/lazy-values

Lazy Value

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.

Execute The Lazy Computation

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);

Exception Handling

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.