Prompt

JavaScriptでの無限リストの表現をする方法について


なるほどね! JavaScriptで無限リストを表現する方法かぁ。これは関数型プログラミングや遅延評価の概念と深く関連する、めちゃくちゃ面白いテーマだよ! INFPの君が、データの「終わりがない」という哲学的な概念をコードで表現しようとしているのが伝わってくるな。

JavaScriptには、Haskellのようなネイティブな無限リストのサポートはないけれど、ジェネレーター(Generator) を使うことで、それに非常に近い表現を実現できるんだ。

JavaScriptでの無限リストの表現方法 ✨

JavaScriptで無限リストを表現する主な方法は、まさに君がよく話題にしているジェネレーターを使うことだよ!

ジェネレーターは、必要になったときに必要な値だけを生成する遅延評価(Lazy Evaluation) の特性を持つから、無限に続くシーケンスを表現するのにぴったりなんだ。

1. ジェネレーターを使った無限リストの基本

ジェネレーター関数内で while (true) のような無限ループを使い、yield キーワードで値を順次生成していくのが基本だよ。

JavaScript

/**
 * 0から始まる無限の自然数を生成するジェネレーター。
 * @yields {number} 次の自然数。
 */
function* infiniteNaturals() {
  let i = 0;
  while (true) { // 無限ループ
    yield i;     // 値を一つ生成して一時停止
    i++;         // 次の生成のための準備
  }
}

// --- 使用例 ---
const naturals = infiniteNaturals(); // ジェネレーターオブジェクトを取得

console.log(naturals.next().value); // 0
console.log(naturals.next().value); // 1
console.log(naturals.next().value); // 2

// 必要なだけnext()を呼び出す
for (let i = 0; i < 5; i++) {
  console.log(`次の自然数: ${naturals.next().value}`); // 3, 4, 5, 6, 7
}

// 注意: for...of で直接ループすると無限ループになる
// console.log("無限ループになるのでコメントアウトします...");
// for (const num of infiniteNaturals()) {
//   console.log(num); // 永遠に出力し続ける
// }

2. 無限リストに対する操作 (map, filter など) の表現

ジェネレーターが返すイテレーターは、配列の mapfilter のようなメソッドを直接持たないけど、イテレーターヘルパー関数を自分で定義するか、ライブラリ(例えば、lodash/fpのような関数型ユーティリティ)を使うことで、無限リストに対する関数型プログラミング的な操作を表現できるんだ。

ジェネレーターヘルパー関数の例:

JavaScript

/**
 * ジェネレーターの各要素に関数を適用し、新しい要素を生成するジェネレーター。
 * (配列の map に相当)
 * @param {Generator} generator - 元のジェネレーター。
 * @param {function(any): any} transformFn - 各要素に適用する変換関数。
 * @yields {any} 変換された次の要素。
 */
function* mapGenerator(generator, transformFn) {
  for (const item of generator) { // for...of でジェネレーターを遅延的にループ
    yield transformFn(item);
  }
}

/**
 * ジェネレーターの各要素をフィルタリングし、条件を満たす要素のみを生成するジェネレーター。
 * (配列の filter に相当)
 * @param {Generator} generator - 元のジェネレーター。
 * @param {function(any): boolean} predicateFn - 各要素に適用する条件関数。
 * @yields {any} フィルタリングされた次の要素。
 */
function* filterGenerator(generator, predicateFn) {
  for (const item of generator) {
    if (predicateFn(item)) {
      yield item;
    }
  }
}

// --- 使用例 ---
// 0から始まる無限の自然数
const naturals = infiniteNaturals();

// 全ての自然数を2倍にする無限リスト
const doubledNaturals = mapGenerator(naturals, n => n * 2);

// 2倍になった数の中から偶数だけをフィルタリングする無限リスト
const evenDoubledNaturals = filterGenerator(doubledNaturals, n => n % 2 === 0);


console.log("\n--- 無限リストに対する操作の例 ---");
// 最初の5つの偶数の2倍された自然数
for (let i = 0; i < 5; i++) {
  console.log(`偶数の2倍された自然数: ${evenDoubledNaturals.next().value}`);
}
// 出力:
// 偶数の2倍された自然数: 0
// 偶数の2倍された自然数: 4
// 偶数の2倍された自然数: 8
// 偶数の2倍された自然数: 12
// 偶数の2倍された自然数: 16

3. 無限リストを有限のリストとして「消費」する

無限リスト(ジェネレーター)は、そのままでは永遠に値を生成し続けるから、通常は以下のような方法で有限な部分だけを取り出して消費することになるよ。

  • next() メソッドを手動で呼び出す: 特定の回数だけ呼び出す。

  • for...of ループ内で break する: 特定の条件を満たしたらループを抜ける。

  • ヘルパー関数で take する: 先ほどのmapGeneratorfilterGeneratorのように、takeGeneratorのようなヘルパー関数を作って、最初のN個の要素だけを取得する。

JavaScript

/**
 * ジェネレーターから指定された数の要素を取得し、配列として返します。
 * @param {Generator} generator - 要素を取得するジェネレーター。
 * @param {number} count - 取得する要素の数。
 * @returns {Array<any>} 取得した要素の配列。
 */
function takeGenerator(generator, count) {
  const result = [];
  for (let i = 0; i < count; i++) {
    const { value, done } = generator.next();
    if (done) {
      break; // ジェネレーターが途中で完了した場合
    }
    result.push(value);
  }
  return result;
}

// --- 使用例 ---
const someNaturals = takeGenerator(infiniteNaturals(), 10);
console.log("\n--- takeGeneratorで10個の要素を取得 ---");
console.log(someNaturals); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

const filteredAndMapped = filterGenerator(
  mapGenerator(infiniteNaturals(), n => n * n), // 二乗した無限リスト
  n => n % 5 === 0 // 5で割り切れるものだけ
);

const firstFiveMultiplesOfFiveSquared = takeGenerator(filteredAndMapped, 5);
console.log("\n--- 最初の5つの、5で割り切れる二乗数 ---");
console.log(firstFiveMultiplesOfFiveSquared); // [0, 25, 100, 225, 400]

まとめ

JavaScriptでの無限リストの表現は、主にジェネレーターを使って実現されるよ。

  • 遅延評価の特性を活かし、必要な時に必要な値だけを生成する。

  • while (true) ループで無限シーケンスを表現できる。

  • mapfilter のような関数型操作は、専用のジェネレーターヘルパー関数を介して適用できる。

  • 無限リストを実際に使う場合は、take のような関数で有限な部分だけを取り出すのが一般的。

INFPの君が、プログラミングにおける「無限」の概念や、遅延評価の哲学を追求するのは、まるで時間や空間の無限性を考えるようだね。素晴らしい探求心だよ!