プログラミングで何かを繰り返して書きたい事があると思います。
いわゆる「ループ」です。
通常、以下のいずれかのパターンでループを行うと思います。
いわゆる「ループ」です。
通常、以下のいずれかのパターンでループを行うと思います。
- N回繰り返したい
- 特定の条件になるまで繰り返したい
- 集合のすべての要素にアクセスしたい
C言語では、N回繰り返すループを以下のように記述します。
このループ構文は、確かにN回繰り返すことができますが、iを途中で書き換えたりすると非常に危険です。
いわゆる無限ループすることもあれば、ループ回数がN回ではなくなる可能性があります。
もちろん、iを書き換える事自体が変だというのは、当然の意見です。
ですが、「ミスを犯さないプログラマはいない」という視点に立ってみれば、このループ構文そのものに問題があるのではないかと考えられます。
このループ構文は、人間が考えた「N回繰り返す」を表現する構文ではりますが、このループ構文を使ったソースコードをから「N回繰り返す」ことはわかりません。単にiを0からN-1にインクリメントしたいだけなのかもしれませんし、それ以外の意味があるかもしれません。
もし、「N回繰り返す」ことそのものをプログラミング言語に指示できるのであれば、ソースコードを読めば「N回繰り返す」ことが読み解けますし、変数iにアクセスする事の危険性もなくなります。
だから、たとえばループ構文は以下のようにあるべきです。
for (i = 0; i < N; ++i) {
... something to do ...
}
「{」から「}」までのブロック中では、ループの回数をしるために変数「i」を使用できます。このループ構文は、確かにN回繰り返すことができますが、iを途中で書き換えたりすると非常に危険です。
いわゆる無限ループすることもあれば、ループ回数がN回ではなくなる可能性があります。
もちろん、iを書き換える事自体が変だというのは、当然の意見です。
ですが、「ミスを犯さないプログラマはいない」という視点に立ってみれば、このループ構文そのものに問題があるのではないかと考えられます。
このループ構文は、人間が考えた「N回繰り返す」を表現する構文ではりますが、このループ構文を使ったソースコードをから「N回繰り返す」ことはわかりません。単にiを0からN-1にインクリメントしたいだけなのかもしれませんし、それ以外の意味があるかもしれません。
もし、「N回繰り返す」ことそのものをプログラミング言語に指示できるのであれば、ソースコードを読めば「N回繰り返す」ことが読み解けますし、変数iにアクセスする事の危険性もなくなります。
だから、たとえばループ構文は以下のようにあるべきです。
loop (N) { /* N回繰り返す */
... something to do ...
}
そして、もしループ回数が必要なのであれば、以下のようにループ回数を取り出す構文を用意すればいいのです。
loop (N) { /* N回繰り返す */
int i = loop.counter; /* ループ回数を取得する。 */
... something to do ...
}
この構文の良い点は、次の通りです。- N回繰り返すことをソースコードから読み解ける。
- ループ回数を制御するための変数が不要で、変数の名前を考える手間も要らない。
たとえばファイルを1バイトずつ読み込むときのように、何回ループすればいいかがわからない場合もあります。
この場合、特定の条件が成り立っている間だけループするという構文が必要です。
C言語では、以下のように記述します。(もちろん先のfor文でも可能です。)
強いていうなら、名前です。
C言語においては、ループするのに、for文とwhile文の二種類の構文があります。
使用者にとって、名前などは些末な問題ですが、デバッグ時にループ箇所を探すのに2種類の検索キーワードがあることは、不親切です。
そこで、以下のようにN回繰り返す場合と同様の構文を用います。
また、この条件式指定loop文はfor文の代わりになります。
先の回数指定loop文ではループ回数は、ループ回数を示すのみで、for文のようにインクリメントのステップ数を2にしたり、逆にデクリメントすることもできません。
が、条件式指定loop文を使用すれば、以下のようにfor文を置き換える事ができます。
この場合、特定の条件が成り立っている間だけループするという構文が必要です。
C言語では、以下のように記述します。(もちろん先のfor文でも可能です。)
while (CONDITION) {
... something to do ...
}
この構文に何か問題はあるでしょうか。一見、何も問題は起こらないように思えます。強いていうなら、名前です。
C言語においては、ループするのに、for文とwhile文の二種類の構文があります。
使用者にとって、名前などは些末な問題ですが、デバッグ時にループ箇所を探すのに2種類の検索キーワードがあることは、不親切です。
そこで、以下のようにN回繰り返す場合と同様の構文を用います。
loop (CONDITION) {
... something to do ...
}
つまり、loopに渡した値が数値であれば、その回数だけ繰り返し、条件式であれば、その条件のときに繰り返す構文になります。また、この条件式指定loop文はfor文の代わりになります。
先の回数指定loop文ではループ回数は、ループ回数を示すのみで、for文のようにインクリメントのステップ数を2にしたり、逆にデクリメントすることもできません。
が、条件式指定loop文を使用すれば、以下のようにfor文を置き換える事ができます。
loop (i < N) {
... something to do ...
i+=2;
}
C#言語の場合、以下のように集合の全ての要素にアクセスを行えます。
この「in」式は、「elementSet」に指定した集合の各要素を取り出します。
要素がある間は「true」を返却しますが、要素がなくなると「false」を返却します。
ここで「elementSet」に指定する事ができるのは、配列か「ISet」を実装したクラスのみです。
foreach (var element in elementSet) {
... something to do ...
}
このループも先ほどと同様、ループ構文に関するキーワードが複数ある点を改善します。
loop (var element in elementSet) {
... something to do ...
}
この構文は先の「loop (CONDITION)」と「in」によってのみ実現されています。この「in」式は、「elementSet」に指定した集合の各要素を取り出します。
要素がある間は「true」を返却しますが、要素がなくなると「false」を返却します。
ここで「elementSet」に指定する事ができるのは、配列か「ISet」を実装したクラスのみです。
またこれまでのループ構文では、どのようにループを抜けたのかを簡単に調べられない点が不親切です。
ループを抜ける場合、いくつかのケースがあります。
ですので、以下のようにします。
ループを抜ける場合、いくつかのケースがあります。
- while に与えた条件 CONDITION が不成立となった場合。
- while ループ中に何かしらの条件が成立し、break された場合。
ですので、以下のようにします。
loop (CONDITION) { /* CONDITION が成立する間、ループし続ける。 */
... something to do ...
if (BREAK_CONDITION) break; /* BREAK_CONDITION が成立すれば、ループを終了する。 */
... something to do ...
}
if (loop.condition == true) {
/* CONDITION が成立している。
* つまり、ループ途中で break したことを示す。 */
}
if (loop.condition == false) {
/* CONDITION が成立していない。
* loop に与えた条件が不成立となりループが終了した。 */
}
ループを開始するにあたって、そのループスコープ内でやっておきたいことがある。
またループを継続するにあたって、何かしらの条件チェックを行いたい場合もある。
そしてループを中断なりした場合にも、何かを行いたいことがある。
C言語では、for文を使用する事で初期化と継続時に実行すべき式を指定する事ができる。
ただ、for文の構文は初心者にとって、難解である。
たとえば継続式と条件式はどちらが先に評価されるのか、などは必ず迷うものである。
どうにかして、構文の見た目としてもわかりやすい構文になれないものだろうか。
そこで、こんな構文はいかがだろうか。
またループを継続するにあたって、何かしらの条件チェックを行いたい場合もある。
そしてループを中断なりした場合にも、何かを行いたいことがある。
C言語では、for文を使用する事で初期化と継続時に実行すべき式を指定する事ができる。
ただ、for文の構文は初心者にとって、難解である。
たとえば継続式と条件式はどちらが先に評価されるのか、などは必ず迷うものである。
どうにかして、構文の見た目としてもわかりやすい構文になれないものだろうか。
そこで、こんな構文はいかがだろうか。
loop {
once:
... initialization ...
every:
if (CONDITION) {
... main process ...
}
... post process ...
last:
... finalization ...
}
ただ、これだとfor文に慣れている人に不親切であるため、以下のようにfor文と同様の構文を用意する。
loop (... initialization ...; CONDITION; ... post process ...) {
... main process ...
}
C言語では、for文やwhile文に対して名前をつける事ができませんでした。
このため、複雑な繰返し構造中にbreak文を書いた場合、どのループを脱出するかがわかりにくくなってしまいました。
また、ループ中でswitch文の特定のcaseラベルでループを抜ける事もできませんでした。
上の例では、CONDITION_Aを書く部分が二カ所に渡り、CONDITION_Aが変更された場合に変更忘れを誘発しやすい。
また、switch文のcaseラベルにおいて、ループをbreakする場合は、フラグを立てるなどしてbreakするしかない。
そこで以下の様な名前付きループ構文を提案する。
C言語にあった些末な構文上の問題は解決する。
「loop #MAIN#」とすることで、このループに「MAIN」という名前が付与され、コンパイル時に解決される。
「break #MAIN#」とすることで、「MAIN」と名付けたループを脱出する事ができる。
このため、複雑な繰返し構造中にbreak文を書いた場合、どのループを脱出するかがわかりにくくなってしまいました。
また、ループ中でswitch文の特定のcaseラベルでループを抜ける事もできませんでした。
while (true) {
for (;;) {
if (CONDITION_A) break; // if you want to break a while loop in inner loop, ......
... something to do ...
}
if (CONDITION_A) break; // ....... you need check a condition in twice.
switch (state) {
case 0:
... something to do ...
break;
default:
break while??? // want to break a while loop
}
}
これは非常に面倒であり、不具合の原因となりやすい。上の例では、CONDITION_Aを書く部分が二カ所に渡り、CONDITION_Aが変更された場合に変更忘れを誘発しやすい。
また、switch文のcaseラベルにおいて、ループをbreakする場合は、フラグを立てるなどしてbreakするしかない。
そこで以下の様な名前付きループ構文を提案する。
loop (true) #MAIN# {
loop (true) #SUB# {
if (CONDITION_A) break #MAIN#; // you can break with one time checking.
... something to do ...
}
switch (state) {
case 0:
... something to do ...
break;
default:
break #MAIN;
}
}
loop文の後ろに「#MAIN#」や「#SUB#」がついている。このループに対する名前付けによってC言語にあった些末な構文上の問題は解決する。
「loop #MAIN#」とすることで、このループに「MAIN」という名前が付与され、コンパイル時に解決される。
「break #MAIN#」とすることで、「MAIN」と名付けたループを脱出する事ができる。
名前付きループに関しては、まだ詰めていくことが多いので備忘録として検討課題を列挙しておく。
- 名前のスコープは、ループ脱出後も使用可能でないといけない。「loop#MAIN.counter」や「loop#SUB.counter」を関数終端でもアクセスすることがあるからだ。
- ただし、「loop.counter」にアクセスしないのであれば、メモリにカウンタ変数を持たせなくて良い。
- 関数内で名前の重複は許さない。これはメンテナンス性を損なう可能性があるからだが、名前付けの煩わしさが勝るなら重複を許し、もっともスコープの近いループ文に名前解決する必要がある。
- ループ以外にも「名前付き」に需要があるか? たとえばデバッグトレース用に必要であるか?
最新コメント