「制御構造」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
Addbot (会話 | 投稿記録)
m ボット: 言語間リンク 18 件をウィキデータ上の d:q868299 に転記
61行目: 61行目:
== 必要最小限の構造化制御フロー ==
== 必要最小限の構造化制御フロー ==
{{See also|構造化プログラミング}}
{{See also|構造化プログラミング}}
[[1966年]]、Böhm Jacopini Communications of the ACM 誌で論文を発表し<ref>Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.</ref>、'''goto''' を使って書かれたプログラムが選択 (IF THEN ELSE) とループ (WHILE condition DO xxx) のみを使って goto を使わずに書き換えられることを示した(コードの一部を複製したり、[[真理値]][[フラグ (コンピュータ)|フラグ]]変数を追加する必要がある)。後に彼らは選択もループ(と追加の真理値変数)で置き換え可能であることを示した。
[[1966年]]、ベームジャコピニはACMの通訊誌で論文を発表し<ref>Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.</ref>、'''goto''' を使って書かれたプログラムが選択 (IF THEN ELSE) とループ (WHILE 条件 DO xxx) のみを使って書き換えられることを示した。後に彼らは選択もループ(と追加の真理値変数)で置き換え可能であることを示した。


そのような書き換えが可能という事実は、必ずしもそれが望ましいということを意味したわけではなかった。コンピュータは一種類の命令 (subtract one number from another and branch if the result is negative) さえあれば理論的には何でもできるが、実際のコンピュータは多数の命令を備えている。
そのような書き換えが可能という事実は、必ずしもそれが望ましいということを意味したわけではなかった。コンピュータは一種類の命令 (subtract one number from another and branch if the result is negative) さえあれば理論的には何でもできるが、実際のコンピュータは多数の命令を備えている。

2013年5月6日 (月) 04:10時点における版

制御構造(せいぎょこうぞう)あるいは制御フローとは、計算機科学において命令、また命令型プログラミング宣言型プログラミングにおけるサブルーチン呼び出しを実行または評価する順序を意味する。

命令型プログラミング言語では、制御構文(せいぎょこうぶん)とは命令の実行順序を通常の逐次実行以外の順番に変化させる構文であり、2つ以上の経路のいずれかを選択するものである。正格でない関数型言語では、関数や言語構成物が同様の結果を達成するために存在しているが、それらは必ずしも制御構文とは呼ばれない。

制御構文の種類は言語によって様々だが、大まかに以下のように分類できる。

  • 別の文から実行を継続する(分岐命令、ジャンプ)
  • 何らかの条件が成立したときだけ文の並びを実行する(選択、条件分岐
  • 文の並びを繰り返し実行する(ループ、コードの先頭に近いほうへの条件分岐と等価)
  • 離れた箇所の文の並びを実行し、元の場所に制御を戻す(サブルーチンコルーチン継続
  • プログラムを停止し、その後の実行を防ぐ(停止)

割り込みシグナルは制御フローを変化させる別の機構であり、サブルーチンに似ているが、言語内の制御構文ではなく外部のイベントなどの結果として非同期に発生するものである。自己書き換えコード副作用によって制御フローを変化させることができる。ただし、それを制御構文として明確化することはほとんどない。

機械語アセンブリ言語では、制御命令はプログラムカウンタを変更する働きを持つ。多くのCPUでは制御命令として(条件付きおよび無条件の)分岐命令しか用意されていない。

基本構文

ラベル

ラベルとは、ソースコード上の固定の位置を示す何らかの名前(あるいは数値)であり、ソースコード上の他の箇所の制御構文で参照される。ソースコード上の位置を記録する以外の効果はない。

行番号は一部の言語(FORTRANBASIC)でラベルの一種として使われ、負でない整数がソースコードの各テキスト行の先頭に置かれる。行番号を使用する言語では、連続で実行される文には行番号が増えるように行番号を与える必要がある。ただし、連続な番号である必要はない。例えば BASIC では次のようになっている。

10 LET X = 3
20 PRINT X

CAdaといった言語のラベルは識別子であり、文の前に書かれ、その直後にコロンが書かれる。例えば C では次のようになる。

Success: printf ("The operation was successful.\n");

Algol 60 言語はラベルとして識別子も非負整数も使用可能(どちらもその後にコロンが続く)だが、多くのAlgol系言語では非負整数をラベルとして許容していない。

goto

goto 文は最も典型的な無条件の制御転送である。キーワードとしては大文字だったり小文字だったりする(言語に依存する)が、その形式は以下のようになっている。

   goto label

goto 文は、次に実行する文をラベルが示す箇所の直後の文とする。

ダイクストラを代表とする多くの計算機科学者は、goto 文を有害だとしている。

サブルーチン

サブルーチンには、手続き、ルーチン、プロシージャ、関数(特に値を返す場合)、メソッド(得に何らかのクラスに属する場合)など様々な名称がある。

1950年代、コンピュータのメモリは非常に小さかったため、サブルーチンの第一の目的はプログラムのサイズを削減することにあった。サブルーチンとして書かれたコードをプログラム内のあちこちから使用することでプログラム全体のコードサイズを削減したのである。現在ではサブルーチンはプログラムを構造化するために使われる。すなわち、特定のアルゴリズムを分離したり、特定のデータにアクセスするメソッドを隠蔽したりする。多数のプログラマが共同でプログラム開発をする場合、サブルーチンはある種のモジュール性を提供し、仕事の分割点の役割も果たす。

サブルーチンに引数があればさらに便利になる。多くのプログラミング言語には平方根を求めるサブルーチンが組み込まれており、引数として平方根を求めたい数を与えることができる。

プログラミング言語によっては再帰呼び出しが可能である。つまり、サブルーチンが直接的あるいは間接的に自分自身を呼び出すことができる。クイックソート木構造を探索するアルゴリズムなどは再帰を使った方が素直に表現できる。

サブルーチンを使用すると、引数の受け渡し、サブルーチン呼び出し、コールスタック処理、サブルーチンからの復帰などのオーバヘッドによりプログラム性能が若干低下する。実際のオーバヘッドはハードウェアおよびソフトウェアのアーキテクチャに依存する。コンパイラによってはインライン展開を効果的に使用してオーバヘッドの低減を図るものもある。

プログラミング言語によってはサブルーチンの物理的な最後尾に到達しないとサブルーチンから復帰できない方式のものもある。他の言語には returnexit 文がある。これはサブルーチンの最後尾への分岐と等価であり、制御構造を複雑化するものではない。必要に応じて複数のそれらの文をサブルーチン内に置くことができる。

必要最小限の構造化制御フロー

1966年、ベームとジャコピニはACMの通訊誌で論文を発表し[1]goto を使って書かれたプログラムが選択 (IF THEN ELSE) とループ (WHILE 条件 DO xxx) のみを使って書き換えられることを示した。後に彼らは選択もループ(と追加の真理値変数)で置き換え可能であることを示した。

そのような書き換えが可能という事実は、必ずしもそれが望ましいということを意味したわけではなかった。コンピュータは一種類の命令 (subtract one number from another and branch if the result is negative) さえあれば理論的には何でもできるが、実際のコンピュータは多数の命令を備えている。

Böhm と Jacopini の論文は全てのプログラムから goto 文を無くすことができることを示した。また、他の研究により入り口と出口がそれぞれひとつになっている制御構造が他の構造よりも理解し易いということが示された。特にそのような制御構造はプログラムの任意の箇所に制御構造を乱すことなく挿入可能な点が有利とされた。

実際の制御構文

制御構造を持つ多くのプログラミング言語は制御構造の開始を指定するためのキーワードを持つ。制御構造の終了に対応するキーワードがあるかどうかで言語は分類できる。

終了キーワードがない言語
Algol 60CC++HaskellJavaPascalPerlPHPPL/IPythonPowerShellなど。この種の言語は文の並びをひとまとめ(ブロック)にする何らかの方法を持っている。
  • Algol 60、Pascal: begin ... end
  • C、C++、Java、Perl、PHP、PowerShell: 中括弧を使用 { ... }
  • PL/1: DO ... END
  • Python: インデントのレベルを使用(オフサイドルール参照)
  • Haskell: インデントのレベルか中括弧を使用でき、それらを自由に混合可能
終了キーワードがある言語
AdaAlgol 68Modula-2Fortran 77Visual Basic など。終了キーワードはいくつかの種類がある。
  • Ada、Fortran 90: 終了キーワードは end + 空白 + 開始キーワード。例えば、if ... end if, loop ... end loop
  • Algol 68: 開始キーワードを逆に綴る。例えば、if ... fi, case ... esac
  • Fortran 77: 終了キーワードは end + 開始キーワード。例えば IF ... ENDIF, DO ... ENDDO
  • Modula-2: 開始キーワードに関わらず常に END という終了キーワードを使う。
  • Visual Basic: 制御構造毎に固有の終了キーワード。If ... End If; For ... Next; Do ... Loop; While ... Wend

選択

if-then-(else) 文

条件式と条件付き構文はプログラミング言語の機能であり、プログラマが指定したブーリアン型の「条件」の評価結果の真偽によって異なる計算・処理を実行させる。

IF..GOTO
非構造化言語に見られる形式で、典型的な機械語命令をそのまま実装したものである。条件が真なら指定されたラベル(または行番号)へジャンプ (GOTO) する。
IF..THEN..(ENDIF)
ジャンプに限らず、単純な文や入れ子になったブロックを THEN というキーワードの後に置くことができる。構造化された形式である。
IF..THEN..ELSE..(ENDIF)
上と同じだが、条件が偽の場合の動作も記述できる。これが最も一般的な形式で、様々なバリエーションがある。終了キーワード ENDIF が必要な場合とそうでない場合がある。C言語やそこからの派生言語では終了キーワードは不要で、'then' に相当するキーワードも不要なことが多いが、その場合は条件式を括弧で囲む必要がある。

多くの場合、条件構文は入れ子にでき、内部に他の条件構文を含むことができる。一部の言語は ELSEIF をひとまとめにした ELSEIF を使用可能で、いちいち ENDIF または相当するキーワードを書かなくて済むようにしている。

Pascal: C: シェルスクリプト: Python: Lisp:
if a > 0 then begin
      writeln("yes")
end else begin
      writeln("no")
end
if (a > 0) { 
      printf("yes");
} else {
      printf("no");
}
if [ $a -gt 0 ] 
then
      echo "yes"
else
      echo "no"
fi
if a > 0: 
      print "yes"
else:
      print "no"
(princ
  (if (plusp a)
      "yes"
      "no"))

あまり一般的でないバリエーションとして、以下のような例がある。

  • FORTRANなどの一部の言語では、3方向の分岐を扱う「算術IF文」があり、数値を正か、ゼロか、負か判定して処理を分岐させる。
  • 一部の言語ではif文が関数として実装されており、例えばLISPcond がある。
  • 一部の言語ではif文が演算子として実装されており、例えばC言語の条件演算子がある。
  • PerlではC言語風の if だけでなく、whenunless がある。
  • Smalltalkでは言語の基本構文としてではなく、ifTrueifFalse というメッセージで条件付き実行を実装している。

パターンマッチング

パターンマッチングとは、MLのような一部のプログラミング言語での高度に抽象化された if-then-else の名称である。ここではOCaml言語での例を挙げる。

match fruit with
| "apple" -> cook pie
| "coconut" -> cook dango_mochi
| "banana" -> mix;;

case文とswitch文

switch文(言語によっては「case文」)は、指定された値を指定された定数群と比較し、最初に一致した定数に従ってその後の処理を決定するものである。一般にどの定数とも一致しなかった場合を想定したデフォルト動作を 'else' や 'otherwise' などとして用意しておく。ルックアップテーブルなどを使ったコンパイラ最適化が可能である。動的プログラミング言語では比較対象が定数式である必要はなく、パターンマッチに拡張することが可能である。例えば下記のシェルスクリプトの例で '*)' は任意の文字列にマッチングする正規表現を使ってデフォルト動作を指定している。SQLdecode 文のように、関数形式で実装することもできる。

Pascal: C: シェルスクリプト:
case someChar of
  'a': actionOnA;
  'x': actionOnX;
  'y','z':actionOnYandZ;
  else actionOnNoMatch;
end;
switch (someChar) {
  case 'a': actionOnA; break;
  case 'x': actionOnX; break;
  case 'y':
  case 'z': actionOnYandZ; break;
  default: actionOnNoMatch;
}
case $someChar in 
   a)    actionOnA ;;
   x)    actionOnX ;;
   [yz]) actionOnYandZ ;;
  *)     actionOnNoMatch  ;;
esac

ループ

ループはソースコード上で1回だけ書かれた文の並びを連続して複数回実行することである。ループの「中」のコード(本体と呼び、下記の例では xxx で表されている)は指定回数実行されるか、指定されたコレクションの各要素に対応して実行されるか、何らかの条件が成立するまで繰り返し実行される。無限に繰り返されることもある。

SchemeHaskellのような関数型言語では、ループは明確なループ用構文ではなく再帰呼び出し不動点コンビネータを使用して実現されることが多い。末尾再帰は再帰呼び出しの特殊ケースであり、容易にループに変換できる。

カウント制御ループ

多くのプログラミング言語は指定された回数だけループを繰り返す構文を持っている。以下の例で N が 1 より小さい場合、ループ本体は全く実行されない。カウントは多くの場合増える方向だけでなく減る方向にも設定可能で、1回に増える量も 1 以外に設定できることが多い(Pascalだけが±1にしかできない)。

   FOR I = 1 TO N            for I := 1 to N do begin
       xxx                       xxx
   NEXT I                    end;

   DO I = 1,N                for ( I=1; I<=N; ++I ) {
       xxx                       xxx
   END DO                    }

多くのプログラミング言語では、カウント制御ループでは整数のみが使われる。浮動小数点数はハードウェアの制限により精度に限界がある。従って次のようなループでは、

   for X := 0.1 step 0.1 to 1.0 do

繰り返し回数が9回の場合と10回の場合がある。これは丸め誤差やハードウェアやコンパイラの違いによって変わってくる。さらに言えば、Xに繰り返し加算すると丸め誤差が累積していき、想定した数列である 0.1, 0.2, 0.3, ..., 1.0 からかけ離れていくことがありうる。

条件制御ループ

また、多くのプログラミング言語は指定した条件が変化するまでループを繰り返す構文をもっている。条件のテストがループの先頭にある場合と最後にある場合がある。前者の場合、ループ本体を全く実行しないことがありうるが、後者の場合は少なくとも1回はループ本体を実行する。

   DO WHILE (test)           repeat 
       xxx                       xxx 
   END DO                    until test;

   while (test) {            do
       xxx                       xxx
   }                         while (test);

コントロールブレイク英語版は通常のループ内で値の変化を検出する手段として使われ、値のグループの処理のトリガーとなる。ループ内で変化する値(群)をキーで監視し、可変な値に関連したグループイベント処理へとプログラムのフローを変換する。

   DO UNTIL (End-of-File)
      IF new-zipcode <> current-zipcode
         display_tally(current-zipcode, zipcount)
         
         current-zipcode = new-zipcode
         zipcount = 0
      ENDIF
      
      zipcount++
   LOOP

コレクション制御ループ

一部のプログラミング言語(例えば、AdaD言語SmalltalkPHPPerlObject PascalJavaC#Visual BasicRubyPythonJavaScriptFortran 95 およびそれ以降)では、明示的に配列や集合やコレクションの全要素に対応してループを回すことができる。

   someCollection do: [:eachElement |xxx].
   
   for Item in Collection do begin xxx end;

   foreach (item; myCollection) { xxx }

   foreach someArray { xxx }

   foreach (someArray as $k => $v) { xxx }

   Collection<String> coll; for (String s : coll) {}

   foreach (string s in myStringCollection) { xxx }

   $someCollection | ForEach-Object { $_ }
   
   forall ( index = first:last:step... )

汎用繰り返し構文

C言語の for 文や Common Lispdo のような汎用繰り返し構造を使えば、前述の各種ループもその他のループも実現できる。例えば、複数のコレクションを並列に回したりできる。もっと個別のループ構造がある場合、汎用繰り返し構文よりもそちらを使った方がコードの目的をより明確に表現できる。

無限ループ

場合によっては無限にループする方がプログラムに適していることもあるし、何らかのエラーが発生するまでループするという場合もある。実際、イベント駆動型プログラムサーバなど)はイベント制御ループを永遠に回り続け、プロセスが操作者によって終了させられたときだけループを停止する。

ただし一般には、無限ループはプログラミングのミスで発生する。すなわち、ループ終了条件がループ内で全く発生しないことが原因で意図しない無限ループとなる。

次の繰り返しへの継続

ループ途中でループ処理を中断してループの先頭に戻り、次の繰り返しを開始したい場合がある。言語によってはこれを実現する continue とか skipnext といった構文を用意している。その効果は最も内側のループ本体の実行を途中で止め、そのループの次の繰り返しを最初から行う。もしそのときの実行が最後の繰り返しであった場合、ループそのものを早期に終了させるのと同じことになる。

現在の繰り返しの再実行

Perl や Ruby といった一部の言語では redo 文によって現在の繰り返しを先頭から再実行することができる。

ループの再実行

Ruby では、retry 文でループ全体を最初から再実行することができる。

ループからの早期脱出

カウント制御型ループを使って配列上のデータを検索している際に、必要な要素を見つけたら即座にループから抜け出したいという状況がありうる。プログラミング言語によっては break とか exitlast といった文を用意していて、現在のループを即座に抜けてそのループの直後の文に制御を転送する機能を持っている。サブルーチン内のループで return を使えば、入れ子になったループからも脱出することになる。多次元配列を入れ子になったループで検索している場合、若干複雑になる(「提案された制御構造」の章参照)。

以下の例はAdaを使ったものである。Ada は「ループからの早期脱出」と「途中にテストのあるループ」の両方をサポートしている。どちらもよく似ているが、コードを比較すればその違いがわかる。「早期脱出」では if 文を使うのに対して、「途中のテスト」は独自の構文を使用する。

with Ada.Text IO;
with Ada.Integer Text IO;

procedure Print_Squares is 
    X : Integer;
begin
    Read_Data : loop
        Ada.Integer Text IO.Get(X);
    exit Read_Data when X = 0;
        Ada.Text IO.Put (X * X);
        Ada.Text IO.New_Line;
    end loop Read_Data;
end Print_Squares;

Pythonbreak 文でループを早期脱出したか否かに依存して実行するかどうかが決定される構文を用意している。以下はその例である。

for n in set_of_numbers:
    if isprime(n):
        print "Set contains a prime number"
        break
else:
    print "Set did not contain any prime numbers"

Python では for 文も while 文もこのような else 節を使うことができる。else 節は早期脱出が発生しなかったときのみ実行される。

ループ変化条件とループ不変条件

ループ変化条件英語版ループ不変条件は、ループの正しさを表すのに使われる[2]

現実的には、ループ変化条件とは非負の初期値を持つ整数式である。変化条件はループを回るたびに減少しなければならないが、正しいループ実行の間は負の値になってはならない。ループ変化条件はループが終了するであろうことを保証するのに使われる。

ループ不変条件は、ループを回る前と各反復において真でなければならない表明である。すなわち、ループが正しく終了するには終了条件とループ不変条件が共に真でなければならない。ループ不変条件は、ループ実行中にループの具体的属性を監視するのに使われる。

Eiffelなどのプログラミング言語でループ変化条件とループ不変条件がサポートされている。Javaではアドオンである Java Modeling Language (JML) という仕様で同様のものをサポートしている[3]

サブ言語としてのループ

一部のLISP方言では、ループを記述するための幅広いサブ言語を提供している。初期の例としては Interlisp の Conversional Lisp がある。Common Lisp では loop マクロを使ってそのようなサブ言語を実装している[4]

ループ機能の比較表

プログラミング言語 条件制御ループ ループ 早期脱出 継続 繰り返しの再実行 ループの再実行 ループの正しさの保証
先頭 途中 末尾 カウント コレクション 汎用 無限 [※ 1] 変化条件 不変条件
Ada Yes Yes Yes Yes 配列 No Yes 深い入れ子 No
C Yes No Yes No [※ 2] No Yes Yes 深い入れ子 [※ 3] 深い入れ子 [※ 3] No
C++ Yes No Yes No [※ 2] Yes [※ 4] Yes Yes 深い入れ子 [※ 3] 深い入れ子 [※ 3] No
C# Yes No Yes No [※ 2] Yes Yes No 深い入れ子 [※ 3] 深い入れ子 [※ 3]
Common Lisp Yes Yes Yes Yes Yes Yes Yes 深い入れ子 No
Eiffel Yes No No Yes [※ 5] Yes Yes No 1レベル [※ 5] No No No [※ 6] 整数のみ [※ 7] Yes
F# Yes No No Yes Yes No No No [※ 8] No No
FORTRAN 77 Yes No No Yes No No No 1レベル Yes
Fortran 90 Yes No No Yes No No Yes 深い入れ子 Yes
Fortran 95およびそれ以降 Yes No No Yes 配列 No Yes 深い入れ子 Yes
Haskell No No No No Yes No Yes No [※ 8] No No
Java Yes No Yes No [※ 2] Yes Yes No 深い入れ子 深い入れ子 No 拡張機能 [※ 9] 拡張機能 [※ 9]
JavaScript Yes No Yes No [※ 2] Yes Yes No 深い入れ子 深い入れ子 No
OCaml Yes No No Yes 配列、リスト No No No [※ 8] No No
PHP Yes No Yes No [※ 2][※ 10] Yes [※ 11] Yes No 深い入れ子 深い入れ子 No
Perl Yes No Yes No [※ 2][※ 10] Yes Yes No 深い入れ子 深い入れ子 Yes
Python Yes No No No [※ 10] Yes No No 深い入れ子 [※ 8] 深い入れ子 [※ 8] No
REBOL No [※ 12] Yes Yes Yes Yes No [※ 13] Yes 1レベル [※ 8] No No
Ruby Yes No Yes Yes Yes No Yes 深い入れ子 [※ 8] 深い入れ子 [※ 8] Yes Yes
Standard ML Yes No No No 配列、リスト No No No [※ 8] No No
Visual Basic .NET Yes No Yes Yes Yes No Yes ループの種類毎に1レベル ループの種類毎に1レベル
Windows PowerShell Yes No Yes No [※ 2] Yes Yes No ? Yes
  1. ^ while (true) は無限ループ専用に用意された構文ではないので、ここでは無限ループに含めない。for (式;;式) は無限ループ専用構文
  2. ^ a b c d e f g h 引用エラー: 無効な <ref> タグです。「loop_for」という名前の注釈に対するテキストが指定されていません
  3. ^ a b c d e f C、C++、C# での深い入れ子からの脱出は、ラベルとgoto文を使用する。
  4. ^ C++11標準で、範囲に基づくforループが導入された。STLには std::for_each というテンプレート関数があり、STLのコンテナに対して各要素に単項関数を適用できる[5]。同様の機能はマクロを使っても実現可能[6]
  5. ^ a b カウント制御ループは整数 interval によるイテレーションで実現される。早期脱出は exit に条件を追加することでなされる。
  6. ^ Eiffelには retry という予約語があるが、これはループ制御用ではなく例外処理用である。
  7. ^ ループ変化条件は整数でなければならず、超限的変化条件はサポートしていない。[1]
  8. ^ a b c d e f g h i 深いブレイクを実現するには、例外処理を活用する必要がある。
  9. ^ a b Java Modeling Language (JML) が必要
  10. ^ a b c カウントループは例えばPythonの range() を使って incrementing list や generator でシミュレートされる。
  11. ^ オブジェクト群のイテレーションは PHP 5 で追加された
  12. ^ 言語構文としては存在せず、while 関数を使用する。
  13. ^ 言語構文としては存在しないが、ユーザーが汎用ループ関数を定義できる。
引用エラー: <references> で定義されている name "loop for" の <ref> タグは、先行するテキスト内で使用されていません。

構造化非局所制御フロー

多くのプログラミング言語、特に動的なプログラミングスタイルを指向した言語では、「非局所制御フロー」の構造を持っている。これを使うと実行の流れは現在のコンテキストから離れ、事前に定義された場所から続行される。「条件」、「例外」、「継続」の3種類の典型的な非局所制御構造がある。

条件

PL/I は標準で22種類の条件(ZERODIVIDE、SUBSCRIPTRANGE、ENDFILE など)をサポートし、これを発生(RAISE)させ、ON condition action; で解釈することができる。プログラマは独自の条件を定義することもできる。

構造無しの IF 文のように action にはひとつの文しか書けないので、多くの場合 GOTO 文を使って制御フローを継続する必要がある。

しかし、実装によってはこれは空間と時間を無視できないくらい浪費する(特に SUBSCRIPTRANGE の場合)。多くのプログラマは条件を使わないようコードを書くことが多かった。

典型的な文法例:

 ON condition GOTO label

例外

最近の言語はGOTO文を使用せずに例外処理を行う構造化された制御構造を備えている。

try {
    xxx1                                  // この中のどこかで以下を使用する
    xxx2                                  //     '''throw''' someValue;
    xxx3
} catch (someClass& someId) {             // someClass の場合をキャッチ
    actionForSomeClass 
} catch (someType& anotherId) {           // someType の場合をキャッチ
    actionForSomeType
} catch (...) {                           // 既にキャッチされていない任意の値をキャッチ
    actionForAnythingElse
}

任意の catch 節が上記の例では使用されている。D言語、Java、C#、Python では try 構造に finally 節を追加することができる。try 部分を離れる際にはどういう理由であっても必ず finally 節が実行されることが保証されている。これは処理を終了する際に何らかの高価な資源(オープン中のファイルやデータベース接続)を解放しなければならない場合に便利である。

FileStream stm = null;                    // C# の例
try {
    stm = new FileStream ("logfile.txt", FileMode.Create);
    return ProcessStuff(stm);             // 例外を発生する可能性がある
} finally {
    if (stm != null)
        stm. Close();
}

この例は非常に一般的であり、C# ではこのための特別な構文がある。

using (FileStream stm = new FileStream ("logfile.txt", FileMode.Create)) {
    return ProcessStuff(stm);             // 例外を発生する可能性がある
}

上記の例の using ブロックを離れるとき、コンパイラが自動的に stm オブジェクトを解放する。Pythonの with 文やRubyの File.open へのブロック引数も同様の効果がある。

このような言語はいずれも標準の例外を定義し、それらがどのような状況で発生するかを定義している。ユーザーは独自の例外を発生させることもできる(実際、C++ と Python は任意の型の throw と catch が可能)。

特定の throw にマッチする catch がない場合、マッチする catch が見つかるまで入れ子構造を遡り、サブルーチン呼び出しを遡る。メインプログラムまで遡っても対応する catch がない場合、プログラムは適切なエラーメッセージを出力して停止する。

AppleScript スクリプト言語 は "try" ブロックにいくつかの情報を提供する。

try
    set myNumber to myNumber / 0
on error e  number n  from f  to t  partial result pr
    if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try

継続

非局所制御フローの比較表

プログラミング言語 条件 例外
Ada No Yes
C No No
C++ No Yes
C# No Yes
Common Lisp Yes No
D No Yes
Eiffel No Yes
Haskell No Yes
Java No Yes
Objective-C No Yes
PHP No Yes
PL/I Yes No
Python No Yes
REBOL Yes Yes
Ruby No Yes
Visual Basic .NET Yes Yes
Windows PowerShell No Yes

提案された制御構造

Datamation 誌(1973年12月)に掲載されたふざけた記事で[7]、R. Lawrence Clark は GOTO文を COMEFROM文で置き換えることを提案し、面白い例をいくつか提示した。これは実際に INTERCAL という言語で実装された。この言語はプログラムを可能な限り読みにくくするよう設計されている。

ドナルド・クヌースは1974年の論文 "Structured Programming with go to Statements"[8] でこれまでの説明された制御構造でカバーされていない2種類の状況を提示し、それを実現する制御構造を例示した。有用性にも関わらず、これらの構文は主流のプログラミング言語に実装された例はない。

途中にテストのあるループ

これは1972年にダールが提案した[9]

   loop                           loop
       xxx1                           read(char);
   while test;                    while not atEndOfFile;
       xxx2                           write(char);
   repeat;                        repeat;

もし xxx1 が省略されたら、テストが先頭にあるループとなる。もし xxx2 が省略されたら、テストが最後尾にあるループとなる。while が省略されれば無限ループとなる。このように、このひとつの制御構文で多くのプログラミング言語にある複数の制御構文の代替となる。ありうべき派生としてループ内に複数の while テストを配置することを許すことが考えられるが、その場合は後述の exitwhen の方が適切である。

このような構文を持たない言語では、一般に無限ループとブレイクの組み合わせて等価なコードをエミュレートできる。

while (true) {
    xxx1
    if (not test)
        break
    xxx2
}

Ada言語では、上記のループ構造(loop-while-repeat)の代替として標準の無限ループ(loop-end loop)内で exit when節を使うことで同様の制御構造を実現できる(後述の exitwhen 文と混同しないよう注意されたい)。

with Ada.Text_IO;
with Ada.Integer_Text_IO;

procedure Print_Squares is 
    X : Integer;
begin
    Read_Data : loop
        Ada.Integer_Text_IO.Get(X);
    exit Read_Data when X = 0;
        Ada.Text IO.Put (X * X);
        Ada.Text IO.New_Line;
    end loop Read_Data;
end Print_Squares;

ループの命名(この例では Read_Data)は必須ではないが、ループの入れ子で外側のループまで脱出させることができる。

複数早期脱出と入れ子ループからの脱出

これは 1974年、Zahn が提案した[10]。ここではそれを若干修正したものを示す。

   exitwhen EventA or EventB or EventC;
       xxx
   exits
       EventA: actionA
       EventB: actionB
       EventC: actionC
   endexit;

exitwhenxxx 内で発生しうるイベントを指定するのに使い、イベントはイベント名を文として使用すると発生する。イベントが発生すると対応するアクションが実行され、その後 endexit 後の処理に移る。この制御構造はある状況を識別する部分と、その状況でとるべきアクションを明確に区別することができる。

exitwhen は C++ 言語の try/catch 構造と概念的によく似ているが、サブルーチン呼び出しを超えたり任意の値を渡したりしないので、より効率的と思われる。また、コンパイラは指定されたイベントが全て発生する可能性があり、それらにアクションが対応しているかどうかをチェックできる。

以下の単純な例は2次元配列から特定の要素を取り出すものである。

   exitwhen found or missing;
       for I := 1 to N do
           for J := 1 to M do
               if table[I,J] = target then found;
       missing;
   exits
       found:   print ("item is in table");
       missing: print ("item is not in table");
   endexit;

脚注

  1. ^ Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.
  2. ^ Meyer, Bertrand (1991). Eiffel: The Language. Prentice Hall. pp. 129–131 
  3. ^ Predicates and Specification Expressions in "JML Reference Manual"
  4. ^ Common Lisp LOOP macro”. 2012年9月8日閲覧。
  5. ^ for_each. Sgi.com. Retrieved on 2010-11-09.
  6. ^ Chapter 1. Boost.Foreach. Boost-sandbox.sourceforge.net (2009-12-19). Retrieved on 2010-11-09.
  7. ^ We don't know where to GOTO if we don't know where we've COME FROM. This (spoof) linguistic innovation lives up to all expectations. By R. Lawrence Clark* From DATAMATION, December, 1973
  8. ^ Knuth, Donald E. "Structured Programming with go to Statements" =ACM Computing Surveys 6(4):261-301, December 1974.
  9. ^ Dahl & Dijkstra & Hoare, "Structured Programming" Academic Press, 1972.
  10. ^ Zahn, C. T. "A control statement for natural top-down structured programming" presented at Symposium on Programming Languages, Paris, 1974.

参考文献

  • Hoare, C. A. R. "Partition: Algorithm 63," "Quicksort: Algorithm 64," and "Find: Algorithm 65." Comm. ACM 4, 321-322, 1961.

関連項目

外部リンク