CASE構造

CASE構造とは、C言語などでは"switch()"と表現されているもので、テストされる値に応じて処理が3つ以上に分岐する場合の記述を簡潔にする機構です。Mops/Forthでは、テストされる値は、CASE構造の開始直前のデータスタックの一番上におかれている値になります。現実問題としてはif条項(IF-ELSE-THEN)だけで実現できるものなのですが、見た目の上で、長たらしい入れ子のIF-ELSE-THENは醜いし読みにくいというので、この機構があるというわけです。Mopsには効率化が施された変種がいくつか存在します。

Mopsには実行の効率化を図った変種がいくつか準備されています。変種についてはMops細説 A面を参照してください。ここではForth標準に合致するものを説明します。それは、"CASE"で始まり、"ENDCASE"で終わります。途中の「場合の選別」は、"値 OF [コード] ENDOF"という形になります。簡単なワードを書いてみると、

: ValueCheck  ( n -- )
  CASE
     0 OF cr ." Atai wa 0 desita" cr ENDOF
     1 OF cr ." Atai wa 1 nanoda!" cr ENDOF
     2 OF cr ." 2 datta moyou desu" cr ENDOF
     dup cr . ." wa han-i gai dayo" cr
 ENDCASE
;
このワードは要するに、スタック上の値(テストされる値:以下、テスト値)が、0,1,2だったらそれぞれの値の後のOFからENDOFまでのコードを実行、つまりそれぞれに応じたメッセージを表示して終わり、どれでもないときには、その値をプリントして、"wa hann-i gai day"(は範囲外だよ)と書くというものです。

動作をもう少し一般的に、詳しく説明しましょう。入力されるスタック上の数値は、"OF"の直前に置かれる値と順に比較されていきます。"順に"というのは、初めの値と違うなら、次の"OF"の値と比較し、それも違うなら次の"OF"の前の値と比較し、... ということです。上の例だと、0か、そうじゃなければ1か、そうじゃなければ2か、ということです。

一致する値があったときには、そのOFからENDOFまでのコードが実行された上で、CASE構造を抜けます。つまり、対応するコード実行後は"ENDCASE"の直後にジャンプします。この場合には初めのスタック値は"OF"の時点で落とされます。

一致する値がなかった場合には、初めのスタック値は残されたまま"ENDCASE"の手前まで処理がやってきます。ここには、該当値がない場合に実行されるコードを書くことができます。上の例では、初めのスタック値を印字に利用するために、この値を"DUP"しています。"ENDCASE"にまで処理が到達した場合には、スタック値はその時点で"DROP"されることになっているのです。ですから、該当値がない場合のコードで入力スタック値を利用したいときには、上の例のように、まずその値を複製して増やして使うか、使った後にドロップ用ダミーの値(数値は何でもいい)を一つスタックに残しておかないと、データスタックアンダーフローエラーになります。

比較値は可変

Forth標準のCASE構造は、実行がそれほど最適化されているわけではありませんが、代わりにとても柔軟です。上の例では、0,1,2と確定した値を指標として使いましたが、実はこの値は変数でもよく、さらには、スタックに値を一つ残す普通のワードでもかまいません。入力値は、その変数等が実行時にスタック上に残す値と比較されることになります。指標値が可変ということは、実行時には、いくつかの選択肢で値がダブることもありえます。この場合には、上から順に比較していくという仕様から、実行されるべきコードが決まります。

範囲で指定

「何から何まで」という数値の範囲で場合を指定することもできます。その際には、"OF"の代わりに"RANGEOF"というワードを使います。書き方は、
[下限] [上限] RANGEOF [コード] ENDOF
のようになります。下限上限とも、範囲に含まれます。上の例に「3から5まで」のコードを付け足してみると、
: ValueCheck  ( n -- )
  CASE
     0 OF cr ." Atai wa 0 desita" cr ENDOF
     1 OF cr ." Atai wa 1 nanoda!" cr ENDOF
     2 OF cr ." 2 datta moyou desu" cr ENDOF
     3 5 RANGEOF cr ." 3 kara 5 made no han-i no atai desita" cr ENDOF
     dup cr . ." wa han-i gai dayo" cr
 ENDCASE
;
ということです。

モジュールとして見る

CASE構造もそれ自体一つのモジュールとみることができます。テスト値がこのモジュールに属するのかどうかは私にはよくわかりませんが、可変でないと意味がありません。外部からの入力と考えることもできますが、本来は、このテスト値を得るための評価式からモジュールは始まっていると考えるべきなのでしょう。というのは、内部での実行内容を決めるものなのですからモジュール内部で調達されるべきと考えられるからです。

さて、さらに細かくいうと、各場合の実行コード毎に一つのモジュールであって、CASE構造はそれらを束ねる、もっと抽象化されたモジュールというべきことになるでしょう(この点は"IF-ELSE-THEN"も同様)。大雑把に図で表してみれば

([入力値]) テスト値生成
CASE
<サブモジュール
<サブモジュール
<サブモジュール
([出力値]) <サブモジュール

という感じでしょうか。

各場合の実行コードは入力と出力を持つことができます。入力は、データスタックのテスト値の下方に置かれることになります。CASE構造のモジュール性(情報隠蔽)を考えるなら、どの場合にも同じ個数(およびタイプ)の入力を消費し、同じ個数(およびタイプ)の出力を残すようにすべきでしょう。さらに、もっと大切なのは、このCASE構造がひとつのまとまりとして、ある一つの機能をもつといえることです。「値に応じていろいろなことをする」というのではダメで、その「場合」も含めた一つの機能として文章化できるかどうかということです。例えば上の例だと、いい加減ですが、「入力値のわかるメッセージを表示する」とかなんとか言いくるめられそうです。

あまり教条主義的になるのも問題かとは思いますが、モジュール性を指向するなら、各場合の実行コードというより小さいモジュールを、CASE構造で複数個まとめた後にも、なお一つの機能として把握できることが大切ということになります。裏からいえば、全く異質な機能を一つのCASE構造で束ねたりしてはいけないということになります。これは、C言語でのswitch-case-default構造でも同じことでしょう。もっとも、「異質な」機能かどうかというのは、それらを観察する際の抽象度にもよるわけですが。それに、普通にプログラミングしているときに、ほんとうに全然違う機能をCASE構造でまとめようなどという発想はでてこないものです。条件に応じて違うことをするとはいえ、同じタイミングで実行されることを選択するものであるという前提があるわけですから。ただ、そのCASE構造でまとめられた全体は、一つの機能として何をするのか、を言葉で表現してみるのは、モジュールとしてのまとまりを考えるには案外いい方法かもしれません。

ちなみに、C言語でのMacintoshプログラミングでswitch-case構造が見られる典型的な場所といえば、イベント処理のところでした。つまり、イベントを受け取って、その種類に応じてそれぞれの処理(イベントハンドリング)を実行するわけですね。今は、違うやり方もできるようです。実際PowerMopsは少し違うやり方をしています。






最終更新:2019年03月18日 20:55