※この記事はCやJavaなどCライクな構文のfor文を持つ言語向けの記事です。Python等foreachな言語は対象外です。
今更のようですが、for文とwhile文の使い分け、プログラミングを始めたての頃には誰しも疑問に思ったことでしょう。
世の中の一般的な文献には、for文は繰り返し回数が明らかなときに使用するなどと書かれていることが多いようです。
しかしながら、自分としてはこの説明があまり腑に落ちませんでした。
今日プログラムを書いていて、この違いについてやっと自分の中で一つの落としどころが見つかったので、ここに記録しておこうと思います。
以下、JavaScriptで記述します。
class Iterator {
index = 0;
constructor(list) {
this.list = list;
}
next() {
return this.list[this.index++];
}
}
const iterator = new Iterator([1, 2, 3, 4, 5]);
以上のような実装のIterator
クラスを用意します(実際にはファイルリストなどをイメージしてもらえれば良いです)。ここで、すべての要素を出力しつつ、要素の個数を数える処理を記述したいとします。当初、私は次のようなコードを書きました1。
let count = 0, item;
while (item = iterator.next()) {
console.log(item);
count++;
}
console.log(`個数: ${count}`);
コードをより読みやすくするため、以下のように書き換えてみます。
let count, item;
for (count = 0; item = iterator.next(); count++) {
console.log(item);
}
console.log(`個数: ${count}`);
書き換えてみたのですが・・・このコード、確かに短くはなっているのですが、非常に読み辛いと感じませんか?
恐らくこの理由は、私たちが
for (A; B; C) {}
という文を見たとき、無意識に
For A, while B, do C.
つまりAについて、Bの間、Cしなさいと読んでいるためだと思うのです。このため、実際の式をここに当てはめたとき、文章として成り立たないと強い違和感を覚えます。
もう少し踏み込んだ表現をするのであれば、意識する必要があるのはAの文、つまり初期設定式です。「for」という単語(=~について)が使われている以上、BやC、そしてブロック内の文は嫌でもこのAの部分に束縛を受けます。
先ほどのfor文の例に違和感があったのは、初期設定式は変数count
の初期化に使用されているにもかかわらず、継続条件式やブロック内の文がcount
と全く関係のないものだったからなのです。
まとめ
while文で表現可能な繰り返しのうち、for文で表現するべきものは
- 繰り返し初期に実施すべき式が存在する
- その式の主たる対象が継続条件や再設定式、ループ内の処理においても主要な要素である
を満たすものだということができると思います。
極端な例
逆に言えば、上のルールを満たしさえしていれば、一般に言われる「繰り返し回数がわかっている」を満たさない場合においても、for文を使って書けるものが存在することになります。以下の例はどうでしょうか。
class Iterator {
index = 0;
constructor(list) {
this.list = list;
}
get currentItem() {
return this.list[this.index];
}
next() {
this.index++;
}
}
let count = 0;
for (
const iterator = new Iterator([1, 2, 3, 4, 5]);
iterator.currentItem;
iterator.next()
) {
console.log(iterator.currentItem);
count++;
}
console.log(`個数: ${count}`);
あまり違和感はない・・・ですよね???
-
JavaScriptでは、配列の範囲外のインデックスにアクセスがあった場合、エラーとはならず
undefined
というfalsyな値を返します。このため、next
メソッドは実行されるたびに要素を1つずつ返却し、全て終わるとundefined
を返すという挙動を示します。 ↩