#13 ループ

ループ

分岐と並ぶプログラムらしい制御、それがループです。

書いたとおりに一遍実行したら終わり、というプログラムは単純ですが、それは手作業でも問題ない可能性が低くありません。 しかし繰り返しがあるとどうでしょう。 人間にとって何億回にものぼる繰り返しは途方もない道のりですが、コンピュータにとっては朝飯前です。

ループには大まかに二種類あります。

どちらでもないものは無限ループとなります。 無限ループにする場合は中止す方法を用意する必要があります。

while/untilループ

whileループは条件がの間繰り返します。 ifのループ版と言えます。

以下はiの値が10未満のときにループします。

もしiが決して10未満にならないのであれば、これは無限ループになります。 このような無限ループは恐らくバグです。

Rubyではifのときと同様にwhile ... endの形式を取ります。

なお、Rubyの場合勘違いのしようがない場合(主には条件式にカッコがなく、カッコの必要性もない場合)はカッコは省略できます。 また、PerlやJavaScriptでは条件式のカッコの前にスペースは入れても入れなくても構わないことになっていますが、Rubyでは入れてはいけません。

使えるのは比較条件式だけではありません。 例えば次のコードでは、標準入力から一行ずつ読み込みます(STDIN.gets)。 この読み込んだ値がi代入されることになりますが、RubyではIO#getsもう読み込むものがないときnilを返します。 nilなので、読み込むものがもうなくなるとこのループは終了します。

条件式に必ずしも読み込みをおけるわけではありません。 読み込んだ内容とは別に値を返すため、値を確認する必要があるもの、 あるいは読み込むものがもうないときエラーになるものもあります。

3パートforループ

C言語からある伝統的なループです。 3パートのforループは次のような書式になっています。

for (初期化式;ループ条件式;継続式)

forループに入ったとき、ループ条件を判定する前に初期化式を実行します。

毎回ループを行う前に条件式を実行し評価します。 このとき結果がならループブロックを実行し、なら実行しません。

そしてループを実行し終わったあと、条件式を評価する前に継続式が実行されます。

次の場合、初期化でi0代入され、初期化した段階ではiの値は0なので、i < 10を満たしループブロックが実行されます。 このループブロックを実行し終わったあと、継続式であるi++(i自身を1加算する)が実行されます。

このコードは実質10回実行されます。 ほとんどの場合3パートforループは規定回数の繰り返しのために使われます。

このことからRubyは規定回数繰り返すことのできるイテレータ(後述)を持っており、3パートforがありません。

あまり使われませんが、規定回数のループ以外にも使うことができます。

このPerlコードでは$__BEGIN_という文字列を最初に入れます。 ルーブブロックではprint;していますが、print;print $_;に等しいため、まず_BEGIN_が出力されます。

条件式の/^_END_/$_ =~ /^_END_/に等しく、! /^_END_/! $_ =~ /^_END_/に等しいため、$__END_で始まらなければループブロックが実行されます。

そして次にその判定を行う前に継続式である$_ = <>が行われ、ARGFから一行読み込まれます。 この読み込みはこのループの一番最初には行われません。一番最初に行われることは$__BEGIN_をセットすることです。 その後はこの継続式を繰り返しながら判定を行う、ということになります。

しかし残念ながらこのような用法を私以外が実用コードでしていることは見たことがありませんし、プログラミング言語によってはこのような用法は許されていません。

イテレータ

文のイテレータ

イテレータはコレクションの各要素に対してループを行うものです。

伝統的イテレータは次のようなfor ... inループでした。

この場合、iにその後に続く各値を代入しながらループします。

これはforeachループと名付けられることもありました。 一瞬混乱する名前ですが、“for each → foreach”です。

メソッドとコールバック関数 (Rubyなど)

Rubyの場合、コレクション要素はeachメソッドがあります。 Rubyはメソッドに対してブロックをつけることができるため、シンプルな方法で美しくイテレータを表現できます。 書き方が少し変わっていますが、慣れてしまえばもうほかには戻れないほどの魅力です。

さらにRubyはeachというメソッドがあれば、Enumerableというモジュールをmix-inすることで、様々なコレクション操作メソッドが使えるようになります。 このため、強力なコレクション操作メソッドを自作のクラスで使えるようにするにも、eachメソッドを書いてEnumerablemix-inするだけととても簡単です。

このRubyのコレクション操作は様々な言語に影響を与えました。 例えばJQueryはJavaScriptに対しRubyのようなeachメソッドを追加します。

JavaScriptはRubyのようにコードブロックを書くための構文を持っていません。 そのため、ちょっと見づらく、使いづらいものになっています。

しかし、Rubyのようなイテレータを書くためにJavaScriptにはアロー関数が追加されました。 これは基本部分の対応でもChrome 45, Firefox 22, Edgeと新し目のブラウザでなければ対応していません(Internet Explorerでは使うことができません)。 また、単純にfunction()の書き方の違いではなく、様々な違いがあります。

JavaScript

JavaScriptイテレータは時代に流され様々な変遷をたどっています。

比較的簡単なのはfor .. inです。

しかし、これはコレクションの各要素を返すわけではありません。 オブジェクトの列挙可能プロパティが全て返されます。

オブジェクトを連想配列のように使用した場合はイテレータとして意図したように使用できますが、 配列で使用した場合は配列要素以外が含まれる可能性や、配列の順番にはならない可能性がありました。

また、削除されたものとしてfor each...inもあります。 こちらはfor .. inと違い全プロパティが列挙されます。

比較的新しいのが、forEachメソッドです。 これはRubyのeachに近いもので、配列に対しては速い段階でサポートされました。

しかしコレクション要素に対する汎用性はあまりありません。 新しいブラウザではNodeListに対してもforEachが実装されているのですが、document.getElementsByTagNameなどで取得するとNodeListではなくHTMLCollectionになるため使うことができません。 この場合でもArray.from()を使うことで利用可能にすることはできますが、あまりうまくいっているとは言えないでしょう。

Python

Pythonは構文でイテレータを処理するようになっています。

まずfor ... inですが、これは普通のループではなく、対象として置かれたオブジェクトは__iter__というメソッドが呼ばれます。 これはRubyにおけるeachメソッドのような役割を持ちます。

実は同じような書き方はRubyでもできます。

Pythonの場合はリスト内包表現という書き方も使うことができます。 これは次のようにするものです。

この書き方においては

  1. arrayの各要素を
  2. i代入
  3. func(i)を実行した結果を
  4. 配列にする

というものになります。 Rubyで書くと

ですね。

このリスト内包表現に関してはさらに絞り込みを加えることもでき、 Rubyで言うと “select + map(collect)”相当になります。

しかし、日本人には読みづらい、selectmapの組み合わせしか表現できない、という欠点もあります。 実際、日本人で内包表現を好んで使っている人はやや少数派に見えます。