ループ
分岐と並ぶプログラムらしい制御、それがループです。
書いたとおりに一遍実行したら終わり、というプログラムは単純ですが、それは手作業でも問題ない可能性が低くありません。 しかし繰り返しがあるとどうでしょう。 人間にとって何億回にものぼる繰り返しは途方もない道のりですが、コンピュータにとっては朝飯前です。
ループには大まかに二種類あります。
- 一定の条件の場合は繰り返す、あるいは一定の条件になったらループをやめる
- ある集まり(コレクション)の各要素に対して繰り返す
どちらでもないものは無限ループとなります。 無限ループにする場合は中止す方法を用意する必要があります。
while/untilループ
whileループは条件が真の間繰り返します。 ifのループ版と言えます。
以下はi
の値が10
未満のときにループします。
while (i < 10) {
// ...
}
もしiが決して10未満にならないのであれば、これは無限ループになります。 このような無限ループは恐らくバグです。
Rubyではif
のときと同様にwhile ... end
の形式を取ります。
while i < 10
# ...
end
なお、Rubyの場合勘違いのしようがない場合(主には条件式にカッコがなく、カッコの必要性もない場合)はカッコは省略できます。 また、PerlやJavaScriptでは条件式のカッコの前にスペースは入れても入れなくても構わないことになっていますが、Rubyでは入れてはいけません。
使えるのは比較条件式だけではありません。 例えば次のコードでは、標準入力から一行ずつ読み込みます(STDIN.gets
)。 この読み込んだ値がi
に代入されることになりますが、RubyではIO#gets
もう読み込むものがないときnil
を返します。 nil
は偽なので、読み込むものがもうなくなるとこのループは終了します。
while i = STDIN.gets
# ...
end
条件式に必ずしも読み込みをおけるわけではありません。 読み込んだ内容とは別に値を返すため、値を確認する必要があるもの、 あるいは読み込むものがもうないときエラーになるものもあります。
3パートforループ
C言語からある伝統的なループです。 3パートのfor
ループは次のような書式になっています。
for (初期化式;ループ条件式;継続式)
forループに入ったとき、ループ条件を判定する前に初期化式を実行します。
毎回ループを行う前に条件式を実行し評価します。 このとき結果が真ならループブロックを実行し、偽なら実行しません。
そしてループを実行し終わったあと、条件式を評価する前に継続式が実行されます。
次の場合、初期化でi
に0
が代入され、初期化した段階ではi
の値は0
なので、i < 10
を満たしループブロックが実行されます。 このループブロックを実行し終わったあと、継続式であるi++
(i
自身を1
加算する)が実行されます。
for (i = 0;i < 10; i++) {
// ...
}
このコードは実質10回実行されます。 ほとんどの場合3パートforループは規定回数の繰り返しのために使われます。
このことからRubyは規定回数繰り返すことのできるイテレータ(後述)を持っており、3パートforがありません。
あまり使われませんが、規定回数のループ以外にも使うことができます。
for (my $_ = "_BEGIN_"; ! /^_END_/; $_ = <>) {
print;
}
このPerlコードでは$_
に_BEGIN_
という文字列を最初に入れます。 ルーブブロックではprint;
していますが、print;
はprint $_;
に等しいため、まず_BEGIN_
が出力されます。
条件式の/^_END_/
は$_ =~ /^_END_/
に等しく、! /^_END_/
は! $_ =~ /^_END_/
に等しいため、$_
が_END_
で始まらなければループブロックが実行されます。
そして次にその判定を行う前に継続式である$_ = <>
が行われ、ARGFから一行読み込まれます。 この読み込みはこのループの一番最初には行われません。一番最初に行われることは$_
に_BEGIN_
をセットすることです。 その後はこの継続式を繰り返しながら判定を行う、ということになります。
しかし残念ながらこのような用法を私以外が実用コードでしていることは見たことがありませんし、プログラミング言語によってはこのような用法は許されていません。
イテレータ
文のイテレータ
イテレータはコレクションの各要素に対してループを行うものです。
伝統的イテレータは次のようなfor ... in
ループでした。
for i in A B C
do
print $i
done
この場合、i
にその後に続く各値を代入しながらループします。
これはforeach
ループと名付けられることもありました。 一瞬混乱する名前ですが、“for each → foreach”です。
foreach $i (@somelist) {
# ...
}
メソッドとコールバック関数 (Rubyなど)
Rubyの場合、コレクション要素はeach
メソッドがあります。 Rubyはメソッドに対してブロックをつけることができるため、シンプルな方法で美しくイテレータを表現できます。 書き方が少し変わっていますが、慣れてしまえばもうほかには戻れないほどの魅力です。
do |i|
somearray.each # ...
end
さらにRubyはeach
というメソッドがあれば、Enumerable
というモジュールをmix-inすることで、様々なコレクション操作メソッドが使えるようになります。 このため、強力なコレクション操作メソッドを自作のクラスで使えるようにするにも、each
メソッドを書いてEnumerable
をmix-inするだけととても簡単です。
do |i|
newarray = somearray.map 2
i * end
このRubyのコレクション操作は様々な言語に影響を与えました。 例えばJQueryはJavaScriptに対しRubyのようなeach
メソッドを追加します。
.each(function(i) {
array// ...
})
JavaScriptはRubyのようにコードブロックを書くための構文を持っていません。 そのため、ちょっと見づらく、使いづらいものになっています。
しかし、Rubyのようなイテレータを書くためにJavaScriptにはアロー関数が追加されました。 これは基本部分の対応でもChrome 45, Firefox 22, Edgeと新し目のブラウザでなければ対応していません(Internet Explorerでは使うことができません)。 また、単純にfunction()
の書き方の違いではなく、様々な違いがあります。
.each((i) => {
array// ...
})
JavaScript
JavaScriptのイテレータは時代に流され様々な変遷をたどっています。
比較的簡単なのはfor .. in
です。
for (i in object) {
alert(i)
}
しかし、これはコレクションの各要素を返すわけではありません。 オブジェクトの列挙可能プロパティが全て返されます。
オブジェクトを連想配列のように使用した場合はイテレータとして意図したように使用できますが、 配列で使用した場合は配列要素以外が含まれる可能性や、配列の順番にはならない可能性がありました。
また、削除されたものとしてfor each...in
もあります。 こちらはfor .. in
と違い全プロパティが列挙されます。
for each (i in object) {
alert(i)
}
比較的新しいのが、forEach
メソッドです。 これはRubyのeach
に近いもので、配列に対しては速い段階でサポートされました。
.forEach((i) => {
arrayalert(i)
})
しかしコレクション要素に対する汎用性はあまりありません。 新しいブラウザではNodeList
に対してもforEach
が実装されているのですが、document.getElementsByTagName
などで取得するとNodeList
ではなくHTMLCollection
になるため使うことができません。 この場合でもArray.from()
を使うことで利用可能にすることはできますが、あまりうまくいっているとは言えないでしょう。
Array.from(document.getElemenetsByTagName("p")).forEach((i) => {
alert(i)
})
Python
Pythonは構文でイテレータを処理するようになっています。
まずfor ... in
ですが、これは普通のループではなく、対象として置かれたオブジェクトは__iter__
というメソッドが呼ばれます。 これはRubyにおけるeach
メソッドのような役割を持ちます。
for i in array:
print i
実は同じような書き方はRubyでもできます。
for i in array
puts iend
Pythonの場合はリスト内包表現という書き方も使うことができます。 これは次のようにするものです。
for i in array] [func(i)
この書き方においては
array
の各要素をi
に代入しfunc(i)
を実行した結果を- 配列にする
というものになります。 Rubyで書くと
array.map {|i| func i }
ですね。
このリスト内包表現に関してはさらに絞り込みを加えることもでき、 Rubyで言うと “select
+ map
(collect
)”相当になります。
しかし、日本人には読みづらい、select
とmap
の組み合わせしか表現できない、という欠点もあります。 実際、日本人で内包表現を好んで使っている人はやや少数派に見えます。