goto文+if文=while文、ただし、オブジェクトでgoto文は有害
用語について
以下、「オブジェクト (対象、object)」、「クラス (class)」、「サブクラス (部分クラス、subclass)」、「インスタンス (実例、instance)」、「this」、「new」は1972年版の構造化プログラミングの定義に従う。かっこ内の訳は75年の日本語版*1訳者の武市正人氏による。
準備:goto文とif文とwhile文
goto文について
goto文はジャンプ文であり、指定した名前のラベル文にジャンプすることができる。例えば、以下のような擬似コードでは、Lのラベルが付いた行へ計算が移動する。...の部分はコードが実行されない。
goto L;
...
L: a = x + 1;
if文とwhile文について
詳しい説明を省いて、疑似コードだけを書いていく。if文は条件分岐であり、while文は反復である。
i = 0;
if ( i < 1) {
a = i + 1;
}
while ( i < 1 ) {
a = i +1;
i = i+1;
}
while文の構成について
while文の構成を見てみると、条件式Xが成立する限り、{}で閉じられた中の文が実行される。これはif文と同じ構成に見える。
while (X) { ... }
if (X) { ... }
実際に、if文は一度だけ実行されるwhile文である。以下の擬似コード中のA && Bは「A かつ B」を意味する。AとBの式少なくともどちらか一方でも偽ならば、A && B = 偽である。Aが真であれば、あるいは、そのときにかぎり、Bが判定される。
i = 0;
while (X && i<1) {
//一度だけ実行される
i = i + 1;
}
上のコードはif (X) { ... }に相当する。
さて、とあるコードYを繰り返したいときはgoto文を使って、以下のようにすれば良い。
L: Y;
goto L;
このとき、Yは何度も繰り返される。
ここで、繰り返すgoto文へif文を組み合わせてみよう。
L: if (X) {
Y;
goto L;
}
このようにすれば、条件式Xが成り立つかぎり、Yはずっと繰り返される。ところがXが偽と判定されるやいなや、if文の中のgoto文は実行されず、繰り返しは停止する。
つまり、上記のコードは以下のwhile文と同じ動きをしているのである。
while (X) {
Y;
}
goto文+if文をダイクストラは1967年まで好んで使っていた。
オブジェクトクラスの登場(SIMULA67言語、1967年)
オブジェクトクラスの連接
1967年、SIMULA67言語がリリースされた。この言語には画期的な技術が使われている。
特に、技術者たちが現在「継承 (inherit)」と呼んでいるクラスの「連接 (concatnation)」は、重要な技術である。ダイクストラたちは、72年版の構造化プログラミングでSIMULA67言語と連接を紹介している。
二つのクラスPとSについて、連接している疑似コード*2を次のように書ける。なお、クラスPを「前接クラス (Prefix Class)」、Sをサブクラスと呼ぶ。
class P() {
x = 0;
int procedure y() {
return x;
}
}
class P, S(){
int procedure z() {
return x+1;
}
}
obj :- new S();
obj.y(); // 0を返す
obj.z() // 1を返す
obj.y()のようにドットを使って内部の手続き(procedure)や変数にアクセスする識別子を遠隔識別(remote identtifier)という。SIMULA67では、遠隔識別を使ってobj.x = 10;も可能である。これを構造化プログラミングでは「カルテシアン積(直積)のダイクストラ選択」とみなす。
連接機能により、前接クラスPの変数xは、サブクラスSでも利用可能である。
インスタンスと再帰関数
構造化プログラミングにおけるインスタンスの定義から、クラスとnew生成は再帰関数*3を一回だけ実行したものと同等である。
例えば、以下のコードで、再帰関数Rが最初の一回目を実行したとき、変数xは永続的に0の値を保ち続けるのである。
procedure R() {
x = 0;
R();
}
上記のコードでは、一週目の x = 0はメモリ上で持続する。しかし、現実には変数xはスタックに積まれて、外部からアクセスできない。
手続きの連接
この変数xを使うためには、複数の手続き同士、あるいはブロック同士で連接し、選出するのがよい。すなわち、ブロックAとB同士が連接しているのだとすると、
A:{
x = 0;
}
A, B:{
x = x + 1;
}
B.x; // 1を返す
また、手続きPAと手続きPBが連接しているのだとすると、以下のコードのようになる。
procedure PA:{
x = 0;
}
procedure PA, PB:{
x = x + 1;
}
PB(); // xは1となる
ここで、注意してもらいたいのは、PAとPBの順序がひっくりかえることはありえないということである。かならずPA->PBの順を守る必要がある。この順序を守る必要性については、この記事では詳しく触れない。
実際には、手続き型プログラミングにおいて、手続きとブロックの連接は不可能である。そこで、72年に構造化プログラミングで紹介したクラスプログラミングが必要となる。
オブジェクトとgoto文
さて、オブジェクトを作成するときに、クラス内部でgoto文を活用するとどうなるだろうか。実際に連接したクラスAとB内をgoto文でジャンプするようにしてみよう。わかりやすくするためにコードの右端には行番号を付けてある。
1: class A() {
2: x = 0;
3: int procedure y() {
4: return x;
5: }
6: goto L;
7: }
8: class A, B(){
9: L: x = 0;
10: int procedure z() {
11: return x+1;
12: }
13: }
14: obj :- new B();
15: obj.y(); // ?
16: obj.z() // ?
このコードから言えることは、obj:-new A();を実行したとしてもクラスBの内部で計算が実行されうる。なぜなら、6行目のgoto L;でクラスB内部にある9行目にジャンプしてしまうからだ。クラスAはクラスBに依存してしまっており、他のクラスとの連接は不可能である。たとえば、
class A {... goto L;}
class B {L:x=0;}
class A, C {...}
obj :- new C();
クラスAと連接するクラスCは成立しない。クラスA内部のgoto文がクラスBへジャンプさせてしまうからである。
よって、オブジェクトを扱う場合、goto文は有害である。