Skip to content

遅延評価

FxTS は遅延評価を提供します。 コードを通じて遅延評価がなぜ有用なのかを説明します。

以下のようなコードをよく見かけます。宣言的にコードを書くことで、保守しやすく読みやすいコードを作りたいと考えています。

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

非常に読みやすく見えます。では、どのように動作するか見てみましょう。

イミュータブルとして扱うために、メソッドが進むたびに 新しいサイズの配列が作成され、配列が走査されます。

typescript
[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

すべての配列値を走査するため、 slicefilterのように配列サイズを縮小するロジックは通常前に配置します (そうすることで走査回数を減らせます)。

typescript
[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

現在、配列サイズが非常に小さいので問題ないように見えます。 しかし、サイズが本当に大きくなった場合、命令型プログラミングに戻る必要があるのでしょうか?

FxTS はIterable/AsyncIterableを扱う関数の組み合わせとして使用でき、 この場合、Iterable/AsyncIterableの値を必要な分だけ評価します。

take(2)(2 つの値のみ)が評価され、それ以降の値は評価されません。 また、上記のコードのArray.prototype.filterはすべての値を走査する必要がありますが、 以下のコードは必要な値のみを評価します。filterさえも。

typescript
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 は、大規模または無限の列挙可能なデータセットを表現するのに便利な方法です。

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

Lazy関数の組み合わせは、ジェネレーターのように実際の値を評価しません。 for-ofawait for-ofStrict関数で評価できます。Strict関数はこちらで確認できます。

typescript
const squareNums = pipe(
  range(Infinity),
  map((a) => a * a),
); // まだ評価されていません

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

Lazy 関数はこちらで確認できます。

実用的な例

以下のコードは、より実用的な状況を示しています。

typescript
/**
 * [{
 *   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);

Released under the Apache-2.0 License.