Skip to main content

Lazy Evaluation

FxTS provides lazy evaluation. Let me explain through code why lazy evaluation is useful.

We often see code like the one below. By writing code declaratively, we want to make code that is maintainable and easy to read.

const sum = (a: number, b: number) => a + b;

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
.filter((a) => a % 2 === 0)
.map((a) => a * a)
.reduce(sum);

It looks very readable. Now let's see how it works.

To treat it as immutable, each time the method proceeds, an array of a new size is created and the array is traversed.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
.filter((a) => a % 2 === 0) // [0, 2, 4, 6, 8]
.map((a) => a * a) // [0, 4, 16, 36, 64]
.reduce(sum); // 120

Because it iterates through all the array values, the logic that reduces the size of the array, such as slice and filter, is usually placed in front of the logic (That way you can have fewer traversals).

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
.filter((a) => a % 2 === 0) // [0, 2, 4, 6, 8]
.slice(0, 2); // [0, 2]
.map((a) => a * a) // [0, 4]
.reduce(sum); // 4

Currently, the size of array is very small, so it doesn't seem like a problem. But if the size gets really big, do we have to go back to imperative programming?

FxTS can be used as a combination of functions that deal with Iterable/AsyncIterble, in which case it evaluates the value from the Iterable/AsyncIterable only as needed.

take(2)(only 2 values) are evaluated and no further values are evaluated after that. In addition, the above code Array.prototype.filter needs to traverse all values, while the code below only evaluates the values it needs. Even the filter.

pipe(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
filter((a) => a % 2 === 0), // [0, 2]
map((a) => a * a), // [0, 4]
take(2), // [0, 4]
reduce(sum), // 4
);

FxTS are a useful way to represent large or possibly infinite enumerable data sets

pipe(
range(Infinity),
filter((a) => a % 2 === 0), // [0, 2]
map((a) => a * a), // [0, 4]
take(2), // [0, 4]
reduce(sum), // 4
);

Combinations of Lazy functions don't evaluate actual values like generator. It can be evaluated with a for-of or await for-of, Strict functions. Strict functions can be found here

const squareNums = pipe(
range(Infinity),
map((a) => a * a),
); // not evaluated not yet

const result = pipe(
squareNums,
filter((a) => a % 2 === 0),
take(10),
toArray, // Strict function
);

Lazy functions can be found here

Useful Example

The code below shows a more useful situation.

/**
* [{
* title: string,
* director: string,
* language: string,
* genre: string,
* rating: number,
* ...
* }]
*/
const fetchMovie = async (year: number) =>
fetch(`https://api.movie.xxx/${year}`);

const recommendMovie = async (year: number, rating: number) =>
pipe(
range(year, Infinity),
toAsync,
map(fetchMovie),
map((res) => res.json()),
filter((movie) => movie.rating >= rating),
head,
);

await recommendMovie(2020, 9);