関数型プログラミングにおいて、イテレータとは、入力データのチャンクを順次、純粋に関数的な方法で段階的に処理するための、構成可能な抽象化です。イテレータを使用すると、リソースがデータを出力する方法を遅延的に変換できます。たとえば、入力の各チャンクを取得する際に大文字に変換したり、入力データ全体をメモリにロードせずに最初の5つのチャンクのみに制限したりできます。また、イテレータはリソースのオープンとクローズも担当し、予測可能なリソース管理を提供します。
各ステップで、反復処理対象には、次のデータチャンク、データが利用できないことを示す値、反復処理が終了したことを示す値の 3 種類の値のうちの 1 つが提示されます。反復処理対象は、呼び出し元に対して次に何をすべきかを示すために、次の 3 種類の値のうちの 1 つを返すことができます。1 つは「停止」(最終戻り値を含む)、1 つは「続行」(続行方法を指定)、および 1 つは「エラーを通知」を意味します。後者の 2 つの値は、実質的に反復処理対象の「状態」を表します。反復処理対象は通常、「続行」状態から開始します。
イテレータはHaskellやScala ( Play Framework [ 1 ]およびScalaz内) で使用されており、F#でも利用可能です。[ 2 ]イテレータの実装には、若干異なるさまざまな種類が存在します。たとえば、Play フレームワークでは、非同期処理を実行できるようにFuture を使用しています。
イテレータは、データを提供する他のコードによって呼び出されるため、制御の反転の一例です。ただし、 SAX XML 解析などの他の多くの制御の反転の例とは異なり、イテレータはプロセスに対する制御を限定的に保持します。以前のデータに戻って確認することはできません (そのデータを内部に保存しない限り) が、例外をスローせずにプロセスを正常に停止することができます (例外を例外的なイベントを通知するのではなく、制御フローの手段として使用することは、プログラマからしばしば好ましくないとされています[ 3 ] )。
以下の抽象化は、厳密に言えば反復処理対象を扱うために必須ではありませんが、より便利になります。
列挙子は、任意のデータソースから反復処理対象にデータを供給する際の便利な抽象化です。通常、列挙子はデータソースに関連する必要なリソースのクリーンアップ処理をすべて行います。列挙子は反復処理対象がデータの読み取りを完了したタイミングを正確に把握しているため、リソースのクリーンアップ(ファイルのクローズなど)を早すぎず遅すぎず、まさに適切なタイミングで実行します。しかも、列挙子は反復処理対象の実装を知る必要も、同じ場所に配置される必要もありません。そのため、列挙子と反復処理対象は、関心の分離の一例と言えます。
Enumerateeは、列挙子または反復処理の出力を変換し、その出力を反復処理に渡すための便利な抽象化です。たとえば、「map」列挙子は、各入力チャンクに対して関数をマッピングします。 [ 4 ]
イテレータは、入出力を構成可能かつ正しくするという問題に対する既存の純粋関数型ソリューションの問題から生まれました。Haskell の遅延 I/O では、純粋関数がディスク上のデータをメモリにあるかのように操作でき、ファイルを開いた後に明示的に I/O を一切行わずに済みました。これは一種のメモリマップド ファイル機能です。しかし、一般的には (停止問題のため) ランタイムがファイルやその他のリソースがまだ必要かどうかを知ることは不可能だったため、過剰な数のファイルが不必要に開いたままになり、オペレーティングシステムレベルでファイルディスクリプタが枯渇する結果となりました。一方、従来のCスタイルの I/O は低レベルすぎ、開発者はファイル内の現在の位置などの低レベルの詳細に気を配る必要があり、構成可能性を妨げていました。イテレータと列挙子は、遅延 I/O の高レベル関数型プログラミングの利点と、C スタイルの I/O によって提供される、必要に応じてリソースと低レベルの詳細を制御する機能を組み合わせています。[ 5 ]
Playフレームワークでは、イテレータを使用して、 Webブラウザへの長時間実行されるCometおよびWebSocket接続にデータをプッシュします。
イテレータは、 JSONなどのインクリメンタル解析(つまり、すべてのデータを一度にメモリに読み込まない解析)を実行するためにも使用できます。[ 6 ]
イテレータは非常に汎用的な抽象概念であり、あらゆる種類のシーケンシャル情報処理(またはシーケンシャル/ランダムアクセス混合処理)に使用できます。I/O処理は一切必要ありません。そのため、ネットワークから流入するデータではなく、メモリ内のデータセットを処理するようにイテレータを簡単に再利用できます。
ある意味では、列挙子がデータを1つ以上の反復処理対象の連鎖に送り込むという概念の遠い前身は、オペレーティングシステムのパイプライン概念でした。しかし、一般的なパイプラインとは異なり、反復処理対象は独立したプロセスではなく(したがって、プロセス間通信のオーバーヘッドもありません)、独立したスレッドでもありません。ただし、互いにメッセージを送信するワーカー スレッドの連鎖と同様の方法で作業を実行できます。つまり、反復処理対象はプロセスやスレッドよりも軽量です。独立したプロセスやスレッドの場合とは異なり、余分なスタックは必要ありません。
イテレータと列挙子は、Haskellで使用するためにOleg Kiselyovによって考案されました。 [ 5 ]後に、Scalaz(バージョン5.0。列挙子は存在せず、Scalaz 7で導入されました)とPlay Framework 2.0に導入されました。
イテレータは自由モナドとして正式にモデル化されており、等式法則を検証したり、イテレータを使用してプログラムを最適化するために使用したりすることができます。[ 5 ]