条件構造
【以下【】の中は訳者が付け足したものです。】
決定とは(人間であれ、コンピューターであれ)条件の検証の結果に、ほぼ尽きるといえます。例えば、:部屋を出るとき電灯スイッチがオンである、が真であるならば、部屋を出る際にちょっと寄り道してそのスイッチを押します。言い換えると、あなたは通常の操作の過程で、一定の条件を検証しています。その条件が真であるなら、それに応じてあなたは何かを行います。その条件が偽であるなら、何事もなかったかのように通常の操作を続行します。
このIF...THEN(もし...ならば)決定の構成が、あなたのプログラムが特定の条件を検証する必要があるときに、まさにコンピュータ内で起っていることなのです。— 数値が奇数か偶数か、プログラムのユーザーが正しい答えを打ち込んだかどうか、などなど。
Mopsでは(他のForthでも同様ですが)、IF...THEN決定プロセスは、あなたが知っているかもしれない他の言語とは少し異なっています。それは、スタック指向の故、といえます。IF...THEN構成の正式な説明は、次のようになります。:
IF xx THEN zz ( n -- ) ‘n’ が非-ゼロ (true) ならば、 ステートメント xx が実行され、それに続いてステートメントzzが実行されます; ‘n’ がゼロ (false)ならば、プログラムはステートメントzzを続けます。
Mopsの決定プロセスのIF 部分は、スタックの一つを取り除き、それが非ゼロかどうか(つまり、ゼロでなければ何でもよい) を検証します。スタックの上の数値が実際に非ゼロであれば、IF の直後の演算操作を実行します。それから実行はTHENの後に書かれた演算操作の実行へと続いていきます。しかし、IFステートメントがスタック上のゼロに出会った場合は、IFとTHENで挟まれた全ての演算操作を無視し、THENステートメントよりも後に書かれた演算のみを実行します。 Mopsでは、"THEN"は、検証後にプログラムを継続することを意味します。例えば、「まずこれをして、それから(then)あれをせよ」 のという場合のような意味です。
IF...THEN構造は、ここまでに学んできた演算と同じようには、簡単に実験することはできません。 というのは、この構成は、Mops上で実行する前に、コロン定義かメソッド定義の中でコンパイルされなければならないからです。ここでの例としては、IF...THENステートメントのためのコードをコロン定義の中に置き、実行前にコンパイルしておきましょう。次のように打ち込んで下さい:
: TEST
IF ." There is a non-zero number on the stack."
THEN cr ;
これは、TESTを、スタックの頂上の数値のチェックを実行するワードとして定義しています。その数値が非ゼロならば、そのことを告げる陳述がスクリーン上にあらわれます。スタックのトップがゼロならば、その陳述は現れません。スタックに様々な数値(0や負の数値を含む)をおいてTESTと打ち込むことによって、試してみてください。 (空(empty)のスタックは数値を持っておらず、検証するための数値がないときにIFを実行すると"stack underflow"エラーメッセージが現れることを忘れないで下さい。他方、ゼロとは実際の一つの数値のことであって、スタック上にスペースを占めているものです。)
二つの選択肢
しかし、決定の中には、先に進む前に二つの可能な選択肢を含んでいるために、もっと複雑になっているものもあります。 例として、最も困難な決定の一つをとりましょう。:朝、仕事にいくために起きる場合です。アラームが鳴った後、あなたはベッドで横になりながら、実際に起きるべきか、あと30分寝るかを決めます。あなたのこころは、ある種の条件を検証します。もしも(IF)あなたが今すぐ起きたなら、そのときには(THEN)仕事に間に合うでしょう、が、そうしない場合は(ELSE) あなたは職を失う危険があります。もしも(IF)今すぐ起きるとすれば、そのときには(THEN)お湯をたっぷり浴びることができますが、そうしない場合には(ELSE)残りの家族全員が浴びた後のシャワーの残り数滴を大急ぎで浴びなければなりません。
この種の決定構造はMopsに含まれています。その形式的な記述は次のようになります。:
IF xx ELSE yy THEN zz |
( n -- ) |
‘n’が非ゼロ (true)ならば、xx ステートメントが実行されzzが続きます。;‘n’ がゼロ (false)ならば、 yy が実行され、zzが続きます。 |
【説明と記述がずれているので、ちょっと変えました。】
IF...THEN構造と同じように、この決定プロセスは、決定を行う前、最初にスタックのトップにある数値が非ゼロかどうかを確かめます。そこで、ELSE規定を勘案するように、TESTを再定義しましょう。
: TEST
IF ." Non-zero number on stack."
ELSE ." Zero on stack."
THEN cr ;
三つの数値(1, 0および3) をスタックにおいて、三回testを実行してみましょう。
1 0 3
test
Non-zero number on stack.
test
Zero on stack.
test
Non-zero number on stack.
IF演算は、大抵のMopsの演算と同様、数値のチェックを実行するときにスタック上のトップの数値を取り除くことに注意して下さい。IFで取り除かれる数値がその後の演算にとっても必要である場合には、IFの前でDUPを実行するか、さもなければ、こちらの方が便利ですが、名前付き入力引数んか局所変数を使って値を保存しておくように定義を書き換えて下さい。
真、偽、および比較
IF...THEN構造はスタック上の数値がゼロであるかないかしか決定できないなら、どうしてそれほど便利なものでありうるのか、と不思議に思われるでしょう。このような検査は、二つの整数が互いに等しいかどうか、とか、一方が他方より大きいかどうか、ある数が正であるか負であるか、といった、プログラマーが為さなければならない"現実世界"の決定から見れば、制限的なものであると思われるかもしれません。実際、IF...THEN構造は、現実世界の決定を可能にする完全な決定手続の最後の端で実行されることがしばしばです。この手続の最初の部分は一つまたはそれ以上の比較演算子からなっており、その結果は比較の結果によってゼロか非ゼロになるようになっています。
ゼロ-非ゼロという言い方を単純化するために、MopsはTRUEおよびFALSEという用語に関わるプログラミング言語上の規約を用いています。これらは、Mopsワードでもあり、比較演算子の結果としてスタックに現れる値を表現するものでもあります。 FALSEはスタック上のゼロを表し、;TRUEはスタック上の負の数を含む非ゼロの数を表します。MopsワードTRUEは非ゼロの数を返しますが、つまり、これは、(二進法で)全ての桁が1の数を返すということです。【「どの桁も非ゼロ」とか「ゼロを含まない」ということでしょうか。】少し後で見るように、これは値 -1 に当たっています。
さて、これらのワードを打ち込んでみましょう。:
true false
スタックディスプレイから、FALSEは0に等しく、TRUEは-1に等しいことがわかるでしょう。
これらのワード — というより、それらが表現する数値 — は実際に今まさに検証された条件の象徴表現なので、それらは、ときどき、フラグと呼ばれます。プログラムにおけるフラグとは、一定の条件を記号化するキーの場所に設置される標識(マーカ-)のようなものです。"true"フラグはスタック上に非ゼロの数値があることを意味しています。;"false"はスタック上にゼロがあることを意味しています。(別の言い方の用語としては、ブーリアン(ブール型)があります — これは実際には"フラグ"と同じことを意味しています。)
TRUEとFALSEのこの違いを脳裏に焼き付ける助けとして、もう一度"test"を定義して、IF...THEN...ELSE構造が、スタック上に存在するTRUEおよびFALSEフラグに呼応する手順を強調してみましょう。
: TEST
IF ." True!"
ELSE ." False!"
THEN cr ;
さて、数値0と4をスタックにおいて、前においたtrueおよびfalseフラグはその下にあるままにしておきます。 そして、上の test を4回実行して下さい。
0 4
test
True!
test
False!
test
False!
test
True!
下は、スタック上にある、一つないし複数の数値を検証して、TRUEまたはFALSEフラグをスタックに残す、比較演算子のリストです。 これらの演算はまさに、あなたが、IF...THEN...ELSEのような決定操作を実行する前に、現実世界の整数に対して行うであろうものです。下のスタック注釈には新しい用語が登場しています:"boolean"(ブーリアン)です。これは、結果としては、TRUEまたはFALSEフラグがスタック上に残るということを意味しています。(booleanという用語はGeorge Booleの名前にちなんだものです。彼は、TRUEとFALSEの値を基礎とした論理システムを発展させました。)
0< |
( n -- boolean ) |
‘n’ が0より小さいならTRUEフラグをスタックに残し、それ以外は FALSEフラグを残します。 |
0= |
( n -- boolean ) |
‘n’が0に等しいならTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
0<> |
( n -- boolean ) |
‘n’が0と等しくないならばTRUEフラグをスタック上に残し、それ以外は FALSEフラグを残します。 |
0> |
( n -- boolean ) |
‘n’が0より大きければTRUEフラグをスタック上に残し、 それ以外はFALSEフラグを残します。 |
< |
( n1 n2 -- boolean ) |
‘n1’が‘n2’より小さい場合はTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
<= |
( n1 n2 -- boolean ) |
‘n1’が‘n2’より小さいかそれと等しい場合はTRUEフラグをスタック上に残し、 それ以外はFALSEフラグを残します。 |
<> |
( n1 n2 -- boolean ) |
‘n1’と‘n2’が等しくない場合にはTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
= |
( n1 n2 -- boolean ) |
‘n1’と‘n2’が等しい場合はTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
> |
( n1 n2 -- boolean ) |
‘n1’が‘n2’より大きい場合はTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
>= |
( n1 n2 -- boolean ) |
‘n1’が‘n2’よリ大きいかまたは互いに等しい場合にはTRUEフラグをスタック上に残し、それ以外はFALSEフラグを残します。 |
これらの比較演算における数学については、全てお分かりであろうと思います。これらの演算は、単純な算術演算同様、演算後置法で設定されていることを忘れないで下さい。スタック上に数値をおく順番を覚えておくには、単に、代数の記法での式の見た目を、頭の中で再構成すれば良いのです。例えば、‘n1’が‘n2’より大きいかどうかを知るには、代数の試験法では、
n1 > n2
となるでしょう。Mopsでは、演算記号を右にずらします。:
n1 n2 >
しかしこの場合、Mopsはこのステートメントの有効性を検証しています。数値は、試験されるときにスタックからとられます。このステートメントが真であるなら、TRUEフラグがスタックにおかれます。;そうでないときにはFALSEフラグになります。これで、IF...THENないしIF...THEN...ELSE決定は、問題の数値に関して行うことができるようになるわけです。
入れ子状の決定
一つの定義の中に、一回に一つより多いIF...THEN...ELSE決定を働かせることも可能です。これを実現するには、IF...THEN...ELSEを、もうひとつのそれの中に置きます。例えば、スタックにある数値を調べて、いくつかの条件について検証する一連の決定演算子を設置し、その数値がその条件に適合したのかをスクリーン上に宣言することができます。これをするには、いくつかのIF...THENステートメントを、互いに入れ子状にすることになるでしょう。:
: IFTEST { n -- }
n 0<
IF ." less than "
ELSE n 0>
IF ." greater than "
THEN
THEN ." zero." cr ;
IFTESTは、ある数が正か負かゼロかを調べるように定義されています。数値をスタックに入れてIFTESTを実行してください。正の数、負の数、またゼロについても試して下さい。
上の定義では、数は名前付き入力引数(‘n’)に割り当てられ、両方のIFで検証できるように値は保存されます。;最初のIF検証【厳密には‘0<’】が数値をスタックから取り除いてしまうため、そうしないと、第二のIFで試験するものが残らないからです。ここでその数値はゼロより小さいかどうか調べられます。もしそうなら、"less than zero."が表示されます(というのは、プログラムは二番目のTHENに飛ぶ【それ以後のコードが実行される】からです。)その数値が負でなければ、次に、ゼロより大きいかどうかを見るために、第二の入れ子状のIF...THEN構造において比較が行われます。その数値がゼロより大きいなら、TRUEフラグが第二のIFステートメントに感知され、"greater than zero."と表示されます。もしもその数値がゼロより大きくなかったなら、(ゼロより小さくないことは既に検証済みなのですから)それはゼロに違いないので、"zero."だけがスクリーン上に表示されます。
銘記しておくべく要点は、IF...THEN構造を入れ子にする場合、全てのIFは、同じコロン定義内のどこかに、それに対応するTHENを持たなければならないということです。これらは、数式で括弧が入れ子になるのとそっくりに入れ込まれます:
( a / ( a - ( b * c ) ) + c )
ですから、各THENは、それが連なっているIFと適合しなくてはなりません。 コードを次のように、
IF xx
IF ww
IF uu
ELSE zz
THEN
THEN qq
THEN yy
繋がっているIF,ELSE,THENを対応させて配列するのは、読みやすさという点では、良い考えといえます。後々するはめに陥るであろうバグ取りに際してのこともいうまでもありません。
論理演算子
将来のあなたのプログラムの中で、二つの比較演算が実行され、これらの演算の結果である二つのフラグがスタック上に置かれるというような状況になることも、きっとあるでしょう。そこからプログラムがどのように進行するかは、これら二つのフラグの状態に依存しています。もしも一つのフラグがTRUEで、他方がFALSEであるとした場合、それは、特定の演算が起るには一方だけが真であれば良い、という要請には適合するでしょう(例えば、‘n1’は、‘n2’よりは小さいが、‘n1’はゼロより小さいは成り立たないという場合)。逆に、特定の演算が起るためには両方のフラグがTRUEである必要があることもあるでしょう(‘n1’は‘n2’より小さくかつゼロより小さいという場合)。これらの特別な場合には、論理演算子ANDおよびORを利用できます。:
AND |
( n1 n2 -- n3 ) |
‘n1’と‘n2’にビット毎ANDを実行し、結果をスタックに残します。 |
OR |
( n1 n2 -- n3 ) |
‘n1’と‘n2’にビット毎ORを実行し、結果をスタックに残します。 |
これらの演算はどちらも、二つの数の二進法での構成をみて結果を生産します。ANDについては、結果となる値は、最初の数値と二番目の数値の両方が1である位置(桁)に1があるものとなるでしょう。 ORについては、結果となる値は、最初の数値または二番目の数値のどちらか一方(あるいは両方)が1である位置(桁)に1があるものとなるでしょう。
例として、数値1と3について、ANDおよびORを適用したときに何が起るのかを見てみましょう:
1 3 and . cr
1
1 3 or . cr
3
ANDとORは整数1 と 3に同値な二進数上にこれらの演算を実行します。1の二進法形式は、
0001
3の二進法形式は、
0011
これらの数にANDを実行したとき、その結果は:
0001
となります。というのは、AND演算は、一番右のビット枠で両方の数でビットが1(TRUE)であるがゆえに1(TRUE)を返すからです。
上と同じ二つの数字についてOR演算を(ANDの代わりに)実行したとき、結果は次のようになります。
0011
というのは、OR演算は、二進数での右二つのビットについて、両方または一方の数のビットが1なので、1を返すからです。
これらの演算の名前であるAND、ORは、ときどき、動詞としても用います。"I want to AND 1 and 3"【1と3をANDしたい】のように。
最後にもうひとつ知っておくべき論理演算子があります。ワードXORです("エクスクルーシブ-オア"(排他的または)と読みます)。正式な記述は次のようになります。:
XOR |
( n1 n2 -- n3 ) |
‘n1’と‘n2’についてビット毎XORを実行し、結果をスタックに残します。 |
お分かりのように、ANDやORと全く同じ使い方をします。次を試してみて下さい:
1 3 xor . cr
2
しかし、"普通の"OR演算("インクルーシブ-オア"(包含的または)と呼ばれることもあります)と違って、XOR演算は各数の対応するビットがともに1(TRUE)であるときには0(FALSE)を返します。(言い換えると、一方か、他方かであって、両方ではだめ、ということです。)
上で得た私達の答え2の二進表示は次のようになります:
0010
右から2番目の桁は二つの数値のうちの一方【3】だけが1(TRUE)なので、この桁の答えは"普通の"ORのときと同じように1になります。しかし、一番右のビット桁については、私達の二つの整数値(1と3)の両方が1(TRUE)なので、"普通の"ORのときとはちがって、0(FALSE)になります。
このやり方で、AND, OR, XORを少し実験してみましょう。これらの演算は、あなたがスタックに打ち込んだ10進法の数値の二進法における等価物に対して作用することに注意して下さい。答えの理解が難しいようでしたら、各数を二進法表示に変換した問題を紙に書いて、それから上でみたように、AND, OR, XOR計算を実行してみて下さい。一度この概念が理解できれば、Mopsはこれらの演算をあなたのためにいつも正しく実行してくれると信頼することができます。
CASE判定
プログラムにおいて、次のステップがいくつかの可能性があるうちの一つとして現れ、スタック上にある実際の数値に依存している、というのは、稀なことではありません。— つまり、TRUE (非ゼロ)かFALSE(ゼロ)かだけではない、ということです。例えば、プログラムはあなたに0から9までの数値のどれかを打ち込むように求めることもあるでしょう。大部分の数値についてはそれに続くステップは同じでも、2,6, および7の数値については結果が違うとします。言い換えると、スタック上に2がある場合には、特有の演算操作が起るということです。確かに、比較演算を続けて行い、IF...THEN構造を入れ子にして、数値を絞り込んでいくことはできます(例えば、数値が2より大きいか小さいかを調べるなど)。しかし、たくさんの数値に関して検証するのは面倒です。
Mopsの上のような多重決定の簡約版が
CASE構造です。上の例を用いれば、次のようなワードを定義できるでしょう:
: CASETEST ( n -- ) \ Print Two, SIX, SEVEN, or OTHER
CASE
2 OF ." TWO" ENDOF
6 OF ." SIX" ENDOF
7 OF ." SEVEN" ENDOF
." OTHER"
ENDCASE cr ;
このワードはスタック上の数値をとって、それが2, 6, または 7の場(OF)場合(CASE)かどうかを調べます。特定の場合(CASE)が確証されたなら、分岐して、ENDOF限定詞に至るまでのステートメントを実行します。その点に到達したなら、実行はENDCASEまでジャンプして、CASE構造内のその他のステートメントはすべて無視されます。そのケースも成り立たないときには、実行はENDCASE限定詞に至るまで続けられます。
ENDCASEの前にステートメントが挿入されている(上の例における「." OTHER"」のように)ときには、全ての場合の検証が失敗したときにはいつも、そのステートメントは実行されます。このステートメントは、デフォルトステートメントといわれます。というのは、それは、他になにも実行されなかった場合、自動的に(by default)実行されるからです。
注意:CASEテストはテスト値をスタックに残しますが、最終的には、ENDCASEで落とされます。デフォルトステートメントでは、特に、テスト値を利用したいと思うかもしれません。しかし、あなたがそれを(スタックからとって)使いたいと考えるならば、まず
はじめにテスト値をDUPする(または、ENDCASEで落とされるダミーの値をスタックに置く)ことを忘れないで下さい。
最終更新:2018年12月07日 20:54