イテレータとジェネレータに関する世界観です。関数型プログラミングも参照のこと。
C++のSTLやJava、Python、Rubyなどの多くの言語が提供している機能として、「イテレータ(反復子)」というものがある。
これは、配列やコレクション、コンテナなどの各要素に対して、順にアクセスするための機能。
イテレータを取得して、next()などの関数を呼び出すことで、ひとつひとつの要素に順にアクセスできる。言語によってはfor文で使うこともできる。
また、イテレータと対応して使われることが多いのが、ジェネレータ。これはPythonやC#ではyieldと呼ばれるreturnと良く似たキーワードで行うことができる。一回の繰り返しに対して、一回の「yieldまでの処理」が行われる。よって、イテレータを一回実行すれば、ジェネレータ関数(yieldが記述された関数をジェネレータ関数と呼ぶ)が一回分実行される。
後日注記:ジェネレータとは「生産するもの」といった意味ですが、要するにfor文で反復する要素をyieldで「ひとつひとつ生み出す」ことができるのです。
後日注記:Pythonなどの言語では、イテレータとfor文は密接な関係にあります。Pythonでは、for文そのものが、イテラブルなオブジェクト(イテレータによる操作が可能なオブジェクト)に対する繰り返し実行処理であり、もしかすると「for文はイテレータのための構文」であるとすら言えるかもしれません。for文に与えることができるのはイテラブルなオブジェクト全般であり、リストやタプルや辞書以外であっても、イテレータさえ実装されていれば、Pythonではfor文で操作することができるのです。
下のリンク先はC++による最近のイテレータの解説であり、めちゃくちゃ難しいですが、イテレータの持つ可能性を感じられます。
2023.01.20編集
僕は、イテレータの面白い点は、イテレータのインターフェースを実装したクラスは、共通の反復インターフェースから操作できる、というところにあると思う。
イテレータは単純な反復処理のためにも使えるが、イテレータのインターフェースを実装したクラスを自分で記述することで、「自分独自のイテレータ」を作ることができる。
この時、反復処理の内容で何をやるかは、クラスを作る側の自由。よって、とても高度で複雑な処理を、反復処理の中ですることができる。一見反復処理とは全く異なる処理であっても、イテレータインターフェースが実装されていれば、言語によってはfor文などからそのクラスを利用できる。
僕は、イテレータは反復処理のカプセル化と言えると思う。実装の詳細を知らなくても、共通の繰り返しインターフェースから、あらゆる拡張イテレータを操作できるのである。
また、イテレータの中で使うことができるのが、yield returnすなわちジェネレータである。C#やPythonなどで使えるこの機能は、for文からreturnと同じように値を返すことができるが、一回きりで終わりになるのではなく、繰り返し処理の中でひとつひとつ要素を「生み出す」ことができる。
イテレータとジェネレータを上手く使うことで、どんなデータでも反復処理、すなわち繰り返し処理が簡単にできる。これはfor文やwhile文の「構造化プログラミング」よりも先の「イテレータプログラミング」であると言えると思う。
後日注記:僕は、C#やPythonのジェネレータの良い点は、「言語機能であるfor文と特定のクラスであるイテレータとジェネレータを統合した」ということにあると思う。標準の組み込みのfor文で、繰り返し関数を応用的にインターフェース化し、再利用した。これこそ、オブジェクト指向プログラミングの真価の発揮ではないかと思う。
詳しくは以下の書籍が参考になります。
イテレータは「全ての要素に対して順番に処理する」ことのできる機能ですが、全ての要素をどのように作り出し、そしてどのような順番をその順番とするかは、プログラマに任されています。
そのため、たとえばジェネレータを使って「素数の一覧オブジェクト」を作って、順番に処理する際に素数をひとつひとつ「作り出す」ように反復させることもできます。
また、イテレータを使うことで、繰り返しの数列と処理を分離できます。反復して要素を作成する処理はデータベースの機能を使って、表示処理をHTMLへの出力としたりすることが可能です。
たとえば、C++のSTLコンテナのvectorは、可変長の非連続的データであるため、ポインタをインクリメントしてカーソルに使うことはできません。インデックス変数のインクリメントを使った添え字アクセスも難しいです。
このような時に、STLではイテレータを使って、コンテナの要素をひとつひとつ順番にアクセスできます。
JavaScriptなどにもイテレータはありますが、JavaScriptのイテレータは「繰り返しのみに特化したオブジェクト」とされます。配列からイテレータを作成すれば、これを配列名の代わりにfor文に与えてやれば順序的にアクセスできます。
イテレータは、ジェネレータと組み合わせることで、一行一行読み込むたびにその一行を処理したり、といったことができます。
単に、インデックスや添え字やポインタを使った場合、配列の要素の数や場所が変わることも、データ構造や中身のロジックの変化によっては起こり得ます。このような場合にイテレータを使うことがよいとされます。
詳しくは以下の書籍が参考になります。
ジェネレータはイテレータとともに用意されていることが多い機能。
通常のイテレータと同様、配列のような連続したデータと同じように反復処理をかけることができるが、実際に配列をデータ構造として持っているわけではなく、呼び出されるたびに要素を自動作成することができる。
いわば「反復処理の仮想化」のようなもの。Pythonではfor文に対して関数からyield returnで値を返し続けることができる。
プログラミングにおいては、「順番」すなわち「順序付けされていること」は非常に重要です。
配列や単なる繰り返しだけではなく、探索やソートのようなアルゴリズムにおいても、リストやツリーをトラバース(順序的にアクセス)する場合においても、あるいは並列処理やスケジューリングにおいて実行プロセスを次々と切り替える場合や、イベントループを行う場合、データベースからデータを一行ずつHTMLに変換して表示させる場合など、あらゆるプログラミング技術が「順番」ということに基づいています。
順番は、抽象的なレイヤーにしか存在しないものではなく、たとえばHTMLのヘッダータグから目次を自動作成する場合などは、単なる順序付けではなく、構造的で複雑な順序付けを高度に行う必要があります。
このような場合に、順序をひとつひとつ辿っていくという意味で、イテレータと呼ばれるアクセスの方法が提供されています。
イテレータを使うことで、順序付けされたデータをひとつひとつアクセスすることができます。そのデータが、どのような型のオブジェクトか、あるいはどのような具体的な要素の種類が含まれているか、ということは、イテレータを使う上では重要ではありません。どのような順序付けされたデータに対しても、「次の要素を取り出す」ということができます。
イテレータを使うことにより、順序付けされたデータの型や要素の種類を考えて、別々の書き方をする必要がなくなります。そのため、現在使っているデータの型や要素の種類が変わっても、それを使う側のコードを書き換えることなく、実装の内部だけを書き換えることで対応することができます。このため、さまざまな場所にある点在したコードを書き換えなくても、カプセル化された中の限られた実装部分だけを書き換えることで対応することができます。APIを変えなくても実装の内部だけを変えるだけで済みます。
イテレータを使う上で、データは配列のように静的に変わらず存在している必要はなく、要素ひとつひとつを取り出しながら自動的に要素が生成されるようなデータであっても構いません。これを「ジェネレータ」といいます。その要素にアクセスした時点で要素が生成されるため、最初から要素が変わらず存在しているわけではない、雑多なイベントへの応答などに応用例が考えられます。実際のジェネレータでは、ファイルから一度に全部の行を読み出すのではなく、一行ずつバッファを使って読み出すためにyield returnを使う例などが主に挙げられます。バッファに一気にすべてのデータを格納せず、少しずつ格納してyield returnで返すことができるため、使用メモリが大幅に削減されます。
2023.04.06
C++(STL・ライブラリ)を参照のこと。
Java(コレクションと配列)を参照のこと。
Python入門(制御フロー)やPython入門(関数型プログラミングの機能)を参照のこと。
C#を参照のこと。
反復処理を行うための反復子。
yield returnなどにより、「ひとつひとつの要素をその都度生成する」かのように、for文の繰り返しごとに新しい値を返す。