1. 問題とスコープ
ログ集約システムにおいて、インメモリ配列、重複排除された Set、ページネーション対応ジェネレーターを優先度順に読み込みます。消費側は先頭 100 件のレコードのみを必要とします。遅延評価を維持しながら Iterator.concat を使用し、無限入力、例外処理、早期に消費を停止した際のクリーンアップについて説明してください。
2. 面接官が見ているポイント
- Iterable と Iterator を区別し、
Iterator.concatが Iterable な引数を必要とすることを把握しているか。 - 結果として得られるものが新しい遅延 Iterator であり、消費されたときにのみ順次入力を取得することを説明できるか。
- 無限ソースが存在するとそれ以降のソースが隠蔽されること、および引数リスト自体は有限でなければならないことを理解しているか。
returnによるクリーンアップ、TypeError のケース、互換性フォールバック、および不必要な配列の具体化(materialization)を適切に処理できるか。
3. まず確認すべき質問
- 各入力は反復可能な Iterable ですか、それともすでに進行した 1 回限りの Iterator ですか?
- 消費側はストリーミングインターフェースを必要としていますか、それとも境界で配列を求めていますか?
- 早期終了時にクローズしなければならないファイル、ネットワーク、またはデータベースのカーソルを保持している入力はありますか?
- 対象となるブラウザや Node はネイティブの
Iterator.concatを提供していますか、それともポリフィルが許可されていますか?
4. 30秒での回答
すべての引数が Symbol.iterator を実装していることを確認した上で、Iterator.concat を使って遅延 Iterator を作成します。これは各入力を順番に取得し、消費側が next や take を呼び出すか、結果を展開したときにのみ値を読み込みます。take(100) は値の取得を停止し、基盤となるイテレーターをクローズする機会を与えます。無限入力は最後に配置するか消費を制限します。古いランタイムでは、同じ遅延評価とクリーンアップセマンティクスを持つジェネレーターのフォールバックを使用します。
5. ステップごとの詳細解説
ステップ 1: Iterable の境界を確立する
Iterator.concat は、単に next を持つオブジェクトではなく、Iterable を受け入れます。配列、Set、ジェネレーターオブジェクトがこれに該当します。単体の Iterator は、まず Iterator.from でラップする必要があります。これにより、concat が各入力イテレーターを確実に取得およびクローズできるようになります。
ステップ 2: 遅延合成を構築する
function* pages() {
yield { source: "page", id: 1 };
yield { source: "page", id: 2 };
}
const memory = [{ source: "memory", id: 1 }];
const unique = new Set([{ source: "set", id: 1 }, { source: "set", id: 2 }]);
const merged = Iterator.concat(memory, unique, pages());
const firstThree = merged.take(3).toArray();merged を作成しても、入力は走査されません。toArray によって消費が開始されます。値はメモリ、Set、ページの順に出力され、concat はそれらの重複排除や変換を行いません。
ステップ 3: 消費の状態を理解する
返される Iterator は、現在の入力と位置を保持します。1 つの消費側がイテレーターを進めると、別の消費側には残りの部分しか見えません。再度最初から再生するには、ソース Iterable から新しい Iterator を取得する必要があります。先頭の N 個の値のみが必要な場合は、最初に展開するのではなく、concat の後に take を適用します。
ステップ 4: 無限入力を制限する
いずれかの入力が無限である場合、結果も無限になります。最初の入力が決して終了しない場合、後続の入力には到達できません。有限のバッチを先頭に配置するか、消費側の境界で take、タイムアウト、キャンセルを強制します。引数リストの展開が決して完了しないため、Iterator.concat(...infiniteIterables()) と書いてはいけません。
ステップ 5: クリーンアップ、エラー、およびフォールバック
消費が停止または例外をスローした際、現在のイテレーターは return を実行してカーソル、ファイルハンドル、接続を解放する機会を得る必要があります。カスタムイテレーターは finally 内にクリーンアップ処理を記録できます。ネイティブサポートのないランタイムでは、ジェネレーターが入力を順次 yield* し、return を伝播させることができます。このフォールバック処理において、すべての入力を配列へ暗黙的にコピーしてはなりません。
6. 模範的な回答
Iterable な入力を検証し、Iterator.concatを使って遅延合成を作成し、消費側の境界でのみtake(100)やtoArrayを呼び出します。concat は入力を順番に取得し、それらの値を重複排除しません。最初の入力が無限である場合、後続のソースには到達できなくなるため、消費を制限し、returnを介して早期停止時のクリーンアップをテストします。古いランタイム向けには、ジェネレーターのフォールバックを使用することで、ソースを具体化することなく遅延評価、順序、1 回限りの状態管理、クリーンアップを維持できます。
7. よくある間違い
nextのみを持つオブジェクトを渡す → TypeError →Iterator.fromでラップするか、Symbol.iteratorを実装します。- concat が即座に実行されると思い込む → 予期せぬ I/O が発生する → 消費によって値が取得されることを忘れないでください。
- 無限 Iterable を先頭に配置する → 後続のソースに到達しない → 順序を変更し、消費を制限します。
- 最初からすべてを展開する → メモリとレイテンシの急増 → 明示的な配列の境界でのみ
toArrayを呼び出します。 - 早期停止時の
returnを無視する → カーソルのリーク → カウント用 Iterable をテストし、クリーンアップをアサートします。
8. フォローアップ質問
フォローアップ 1: Array.concat との違いは何ですか?
Array の concat は、具体化された配列を即座に処理して配列を返します。Iterator.concat は Iterable を受け入れ、ジェネレーターや無限ソースを表現できる遅延 Iterator を返します。
フォローアップ 2: なぜすべての Iterator を直接渡さないのですか?
Iterable でない単体の Iterator は、まだ到達していない入力の所有権やクローズ処理が曖昧になります。Iterator.from でラップして Iterable の境界を確立してください。
フォローアップ 3: flatMap が好まれるのはどのような場合ですか?
入力自体が Iterable の大規模または無制限なシーケンスである場合、flatMap はそれらを段階的に生成してフラット化できます。concat は、有限で既知の入力セットに対してより明快です。
フォローアップ 4: クリーンアップをどのように検証しますか?
next と return を実装したテスト用 Iterable を使用し、値の取得とクローズを記録します。そして、take、消費側の中断、コールバックエラーによって値の取得が停止し、return が呼び出されることをアサートします。