IF


IFはもちろん条件分岐のためのワードである。ForthのIFは、他のプログラミング言語とも、また英語とも、語順が異なっている。IFは条件をスタックから取る。したがって、判定値はIFの前に定まっていなければならない。IF以下は、スタック上の判定値が偽でないときのみ実行されるのである。「偽でない」とは0でないという意味である。真(TRUE)は-1で表現されるからもちろん0ではない。しかし、真の場合だけでなく、0でない値なら何でも、IF条件は成立したことになるのである。

IF条項はTHENで閉じる。ForthのTHENは、通常の場合のように「IF条件が成り立った場合」を意味しない。ひとつのIFに関わる部分を終了させるのである。IFの判定値が偽であったときには、次のTHENまでのコードは実行されない。THENをEND-IFと改名して用いている環境もある。

IF文は、コンパイル状態でのみ使用可能である。つまり、ワード定義の中になければならない。
... IF word1 THEN word2
とあった場合、IFの手前までにスタックのトップに置かれる判定値が0でないときはword1続いてword2が実行され、0であるときはword1は飛ばされ、word2から実行されるわけである。

ELSEも定義されている。
... IF word1 ELSE word2 THEN word3
とあれば、判定値が非0ならword1そしてword3が、判定値が0ならword2そしてword3が実行される。
日本語としては、IFは「もし」ではなく「ならば」と読み、ELSEは「そうでなければ」、THENは「さて、それから」と読むと良い。(もっとも入れ子になると無理は出てくるが。)

IF-(ELSE-)THEN構造は、入れ子にもできる。しかし、Forthでは多重のIF-THENは避けるべきものと考えられている。コードを読みにくくするからである。多層的なIF-THEN構造が現れたならば、それはワードを分割すべきサインであるともいわれる。条件分岐は、基本的に、アルゴリズムの構成に応じて現れてくる。複雑なIF-THEN階層が必要となったなら、アルゴリズムの修正や、同じアルゴリズムの意味的な細分の切り口を替えてみるなどの方法を考える切っ掛けとして考えるのは良い結果を招くかも知れない。
(細分の一般的方針については、他のページで述べる。)

MopsにはIFの反転としてNIF がある。Not-IFである。スタック値が0のときのみNIF以下が実行される。ELSEと組み合わせることももちろんできる。使い方はIFと同じである。ただ条件関係が反転するだけである。

EXIT


EXITはワードから抜け出すためのワードである。もちろん、これは何かのワードの定義の中でしか利用できない。

構造化プログラミングという考え方からすれば、IF-THEN構造でキレイに分割整頓されたコントロールフローを形成すべきであるが、EXITはそういった構造化には適合しない。ワードの何処からでも、定義内容の最後(つまりセミコロンのところ)まで跳躍できるものだからである。
: word-ex   ( n -- b )
   dup 0<= if drop false EXIT then
   [入力を加工してどこかに格納] true   ;
このようなワードを定義すれば、スタックからの入力値が0以下であるときには、スタックにfalseを返して、このワードの処理を抜けてしまう。入力が0より大きいときのみ、以降の処理をした上でtrueを返す。

構造的プログラムでは、処理はブロック毎に固めるべきで、跳躍したり漏れだしたりはみ出したりするのは、コードの可読性を阻害すると主張される。けれどもEXITは当のワードの処理を抜けるというだけであるから、経験的にも、それで読みにくくなるということはない。むしろ入れ子のIf-thenの方がたどりにくい。

EXITは、通常、何か条件判定の結果として引き起される。出力や余剰入力の処理など、スタック上の準備をする必要がある場合には、EXITを単独で呼び出す方が便利である。しかし、何もしないで抜ける場面も多い。そこで、Mopsでは、条件次第で直ちに処理を抜けるための短縮ワードとして、?EXIT0EXIT が定義されている。どちらも、判定値としてスタックの値を1つ消費し、 ?EXIT はスタック値が0でないときに処理を抜け、 0EXIT はスタック値が0であるときに処理を抜ける。それぞれ、"IF EXIT THEN"と"NIF EXIT THEN"に同値である。

CASE


スタック上の値の違いに応じて多方向に分岐する場合を簡潔に記述する方法がcaseスイッチである。ForthではCASEというワードが標準的である。この分岐構造は、CASEというワードで始め、ENDCASEで終了する。途中、値に依る分岐をOFとENDOFで囲むことで定義し、最後の分岐のENDOFとENDCASEの間は、どれにも該当しなかった場合がくる。これはデフォルトと呼ばれる。
CASE構造もワード定義内でしか利用できない。

例えば次のような構文になる。
.....
CASE
  0 OF ." It is zero." cr ENDOF
  1 OF ." It is one." cr ENDOF
  2 OF ." It is two." cr ENDOF
  3 OF ." It is three." cr ENDOF
  dup . ." is...hu? I don't understand it." cr
ENDCASE
.....
CASEの手前までにスタックのトップに置かれた値(仮に、入力値と呼ぶ)が、OFの前に書かれた数値(仮に、基準値と呼ぶ)と一致した場合には、その行のOFからENDOFまでが実行された後、ENDCASEまでジャンプする。
その際、注意すべき点は、初めにスタック上にあった入力値はENDCASEで消費されるということである。したがって、分岐の中でも、また特にデフォルトのところでも入力値を利用することはできるが、それを消費する場合にはENDCASE用にdupするかダミーを置かないと、スタックアンダーフローになる。

基準値は、もちろん、負の数でも良い。
上の例で数字を書いた基準値は、変数を用いても良い。その場合には実行時に格納されている値が比較されるので、分岐条件を動的に変えることも可能である。何らかの計算を実施した結果でもよい。その場合にも、実行時の計算結果が参照される。

なお、Mopsでの拡張として、CASE構造の中にはRANGEOFというワードも利用できる。これは、基準値として該当範囲を指定するものである。この分岐もENDOFで閉じる。例えば、上の例に追加して
CASE
  0 OF ." It is zero." cr ENDOF
  1 OF ." It is one." cr ENDOF
  2 OF ." It is two." cr ENDOF
  3 OF ." It is three." cr ENDOF
  4 8 RANGEOF ." Oh, too many!" cr ENDOF
  dup . ." is...hu? I don't understand it." cr
ENDCASE
とすれば、4から8の範囲では、Oh, too many! と印字されるだろう。性格上、数値は2つ必要であるが、前の数が後の数より大きい場合には、決して該当しない。

この方式は特に効率化されておらず、比較判定は上から順に、該当するまで全て実施される。したがって、基準値に重複がある場合は先に書いた方が優先される。

Mops拡張として、各分岐の基準値が定数のみの場合について、その事実を利用して最適化したCASE[-構造や、ジャンプテーブルを用いるSELECT[-構造があるが、その説明については、ここでは省略する。


次は、確定ループについて説明する。


タグ:

forth if exit case
最終更新:2013年06月05日 16:44