条件判定とその結果に応じた処理手順の分岐はプログラムを構成する上で重要な事柄です。ここでは、if条項(文法用語としては「if節」ですか。プログラム的には「条項」の方が囲った感じがあっていいので、これで通します。ちなみに"if clause"を訳しているつもりです。)による分岐を扱います。
Mops/Forthのif条項の特徴
Mops/ForthでのIF条項もまた、IF-ELSEなどの英語の条件語句を用いることについては、通常のプログラミング言語と同様です。ですが、他と比べるとちょっと変わった性質が2つばかりあります。
一つは、IF条項もまたスタック指向であることです。IFは、その時点でのトップスタックの値を真理値として判定に使います。そのため、判定の結果はIFよりも前に出されていなければなりません。つまり、
(判定式) IF コード....
という語順になります。通常のif構文は、英語と同じく"if (判定式)"という順になっていますが、Mops/Forthではそうではないのです。この語順を逆ポーランド記法と言うことはできないと思いますが、それでもMops/Forthの逆ポーランド記法を導いている
データスタック指向のが背景にあることは容易にわかります。
二つ目は、IF条項の終わりを記すために"THEN"というワードを使うことです。つまり、IF条項は全体として次のような形式になるということです。
(判定式)
IF
[真の場合のコード]
ELSE
[偽の場合のコード]
THEN
この場合、もちろん、(判定式)がスタックに残した値が真に当たるものであれば、"[真の場合のコード]"だけが実行されて、次は"THEN"の直後に飛びます。反対に偽であれば、"[偽の場合のコード]"だけが実行されて、次はやはり同じように"THEN"の直後に飛びます。こうして、判定条件に応じて実行内容を変えることができるわけです。
ELSEと"偽の場合のコード"は、無くてもかまいません。つまり、IFとTHENだけでも
"IF"は真偽判定する値をひとつ消費します。ですから、判定に使われた値を残しておきたいときには、予め"DUP"で値を複製しておく必要があります。
なお、この、最後が"THEN"であるのが、欧米言語圏の人には納得いかないのか、"ENDIF"も使えるようにしているForth環境もあるようです。確かに、"endif"なら他の言語でも見かけますね。というか、欧州の人にとってはヨーロッパ発の流行言語、しかも思想的にも非常に"正しい"ものであるPascalへの郷愁があるのかもしれませんね。いや、いまやPascalは再び一大勢力ですが。
ただ、日本人としてみると、"IF --> であるならば"、"ELSE --> そうでないなら"、"THEN --> それから"という直訳でForthの構文も何となく意味が分かるのが不思議です。ちなみに、Forth語順は日本語語順だという意見さえあります。実際、この性格に着目したのかどうかは知りませんが、Forthをもとにつくられた日本語プログラミング言語Mindというのがあるようです。
Compile-Only
なお、IF-THEN条項を含めて、処理手続の流れの向き付けをするワードセットは、制御構造(コントロールストラクチャ)などという名前でまとめられます。他には、Case(switch)構造と
ループも含まれます。こういったワードは、Forth系では処理の分岐をコンパイルするものとなっています。この意味は、これらのワードセットを、直接にインタープリタで実行することはできない、ということです。裏からいえば、何かのワードの定義の中で用いなければなりません。インタープリタで試すときには、これらの諸構造を定義内に含むワードを定義した上で、そのワードを実行するという手順を踏みます。
値の判定と真理値
値の大小関係、あるいは、相等関係を判定するワードは比較的見慣れたものです。これら全て、スタック上の二つの値を消費して、その大小・相等関係に応じて真(TRUE)ないし偽(FALSE)の値をスタックに返します。
< ( x1 x2 -- b ) \ x1がx2より小さいときbはTRUE、それ以外はbはFALSE
<= ( x1 x2 -- b ) \ 上の"以下"版(等しいときもbはTRUEになる)
> ( x1 x2 -- b ) \ x1がx2より大きいときbはTRUE、それ以外はbはFALSE
>= ( x1 x2 -- b ) \ 上の"以上"版(等しいときもbはTRUEになる)
= ( x1 x2 -- b ) \ x1とx2が等しい値のときbはTRUE、そうでないときbはFALSE
<> ( x1 x2 -- b ) \ x1とx2が異なる値のときbはTRUE、そうでないときbはFALSE
基本的なのはこれぐらいですね。
真理値は"TRUE"と"FALSE"ですが、実際には定数値です。具体的には、TRUEは-1、FALSEは0です。高級性を強調する言語では、こういうものは数値としては使えないようになっていたりするんですが、Mops/Forthでは、これらの値としての性質を使うことにあまり抵抗はないようです。その意味ではMops/Forth低級指向といいますか、手っ取り早い方法は捨てないという主義と言えます。
ただし、IF-THEN条項で真として扱われるのは、実はTRUEだけには限られません。この局面では、0でない値は全て真として扱われます。裏から言えば、この局面では0だけが偽として扱われます。実際、スタック上の値が非0であるかどうかというかたちで、真理値に変換しないまま直接にIF-THENに与えることも稀ではありません。
真理値を数値と見うるかどうかというのは、論理的に言えば、高級云々ではなくて、型(タイプ)をどれだけ侵すべからざるものと考えているかどうかの問題です。真理値型と整数型がゴッチャになるのはまずい、というわけです。この意味では、関数型言語やSmalltalkなどは非常に強く型にこだわっています。Smalltalkには、変数を型で縛るということはないんですが、一つ一つがオブジェクトとしてクラスと言う型を持っているわけで、その型には強く規定されています。関数言語系でも、大学系の論文などでは(中もは読んだことがないし、読んでも私にはわからないと思いますが)型推論による効率化云々というのは腐るほどあるようですね(伝聞)。これは、抽象化を論理的に完全な形で行うには型(タイプ)の概念を貫徹することが不可欠であると考えられているからのようです。まあ、「論理学的には」そうせざるをえないというべきでしょうか。つまり、型 -> 型の型 -> 型の型の型 -> ... あるいは、クラス -> クラスのクラス(スーパークラス) -> クラスのクラスのクラス(スーパークラスのスーパークラス) -> ... 等々。いや、ハッキリ言って、初期のLISPなんかは、この階層を工夫もなくマンマ実装したせいで異様に重かったんじゃないかと疑ってるんですがね。
静的な(つまり、不動の)構造とか、諸階層の峻別と秩序の維持とか、理論はそういうことにこだわるようなのですが、少なくとも現在流布されている数学(mathematics)では、これらがないとダメということなのかもしれません。ただ、数学的思考活動そのものは、こういうちょっとカースト風の秩序観ではうまく観察できないことも、最近では周知の事実だと思うんですがね。抽象化って、それだけのもんじゃないだろう、とか思ったりするわけです。
簡単な例
PowerMopsでためすことができる例を作ってみましょう。役には立ちませんが。
スタック上の数値が10より小さかったらそれに1を加えて出力し、10以上の場合は決まって10を出力するというのを書いてみます。名前は"CEILING"にでもしますかね。
: CEILING ( n -- n' ) DUP 10 < IF 1 + ELSE DROP 10 THEN ;
短いんで、1行で書いてみました。スタックの状態をご自身で追ってみてください。これくらい簡単?そうですか。
では、もう少し入り組んだものを。
- スタックの数値が10より小さい場合:
- 5より小さいなら2増やす。5以上なら、1増やした数をスタックに残し、もとの値をMopsウィンドウに表示する。
- スタックの数値が10以上の場合:
- 15より小さいなら10を出力する。15以上なら11を出力し、もとの数をMopsウィンドウに表示する。
スタック上の数値を取り出してウィンドウ上に表示するワードはドット、というかピリオド「.」です。表示の見栄えをよくするために改行するには、ワード"CR"を使います。せっかくなので、"Original is : "というメッセージ文字列も表示します。基準値が小さい順にやるという手もありますが、もとの構造を残すと、
: CEILING2 ( n -- n' )
DUP 10 <
IF
DUP 5 <
IF
2 +
ELSE
DUP 1 + SWAP CR ." Original is : " . CR
THEN
ELSE
DUP 15 <
IF
DROP 10
ELSE
11 SWAP CR ." Original is : " . CR
THEN
THEN
;
ワード「."」は、次のダブル引用符までの文字列をウィンドウに表示します。
ちょっと長たらしいですね。改行しすぎましたか。上のコードではIF-THENが入り組んでいます。Forthではこういうのを嫌う傾向があります。そこで、よーくみると、DUPから始まるIF-ELSE-THENを本体とする区画が一つの箱のようになっているように見えてきませんか。全体が入れ子の箱、みたいな。
それではということで、この部分を独立したワードとして括り出しましょう。それと、文字列表示のための"SWAP"以降がダブってますね。これも一つにしちゃいます。"CR"からの方がいいかもしれませんが、一応、SWAPまで入れておきます。というわけで、
: Disp-Original ( n1 n2 -- n2 ) SWAP CR ." Original is : " . CR ;
: Criterion5 ( n -- n' ) DUP 5 < IF 2 + ELSE DUP 1 + Disp-Original THEN ;
: Criterion15 ( n -- n' ) DUP 15 < IF DROP 10 ELSE 11 Disp-Original THEN ;
: CEILING ( n -- n' ) DUP 10 < IF Criterion5 ELSE Criterion15 THEN ;
ん~。IF-THEN一個でも、改行ないと読みづらいですか?私はそうでもないんですが。ご自身がわかりやすいようにお書きください。三つのワードの初めの部分の数値比較もわかりやすい名前のワードにして書き分けることもできそうですが、そこまでやる必要はないでしょう。一般に、ワードを細かくわけ書きすると、実行効率が若干落ちます。まあ、C言語などで別関数に書き分ける場合程は落ちませんが。それにこの場合は実行効率を落とさずにわけ書きする方法もあります。でも、そうするとコードサイズが少し大きくなってしまいます。上のような役に立たないコードでは大した問題ではないのですが、プログラミングの際には、この手のことも気にしつつ、機能構造に従ってワードを分かち書きするわけです。
アルゴリズム
上に、「スタック上の値が10より小さい場合:...」というような、規定というか、コード内容と言うか、まあ、そういうことを書きましたが、これがアルゴリズムと呼ばれるものです。問題の解法パターンということですね。
アルゴリスム(Algorithm)は、9世紀頃の中東の数学者アル・クワーリズミ(フワーリズミ)(アルファベットだと、Al Khwarizmi)の名前が由来だそうです。Alは英語だと"The"だか"A"だかの、なんか冠詞らしいので、クワーリズミが名前ですね。確か、色々な代数方程式について、そのパターンに応じた解法を整理した本を書いたらしく、その本の名前がAlgebra(代数学)の語源でもあるそうです。
いってみれば、一朗という名前の人が何か決定版の参考書をかいて、みんながそれを読んで勉強するので、「じゃあ、まず、この問題の一朗式解法パターンを考えてみよう」とかいっても通じるようになって、そのうち、「一朗」という名前はなんか読みにくいので少し訛ってきて、だんだん、それが人の名前だったことさえ忘れられて、「え~っとお、この問題のイーチャロンはあ、」とか言う学生も普通になった、というかんじですかね(^^;;)。
上の場合、初めにアルゴリズムが与えられて、その手順をそのままプログラミング言語で移し替えた感じになります。実際の世の中の問題はコンピュータアルゴリズムの形で与えられるわけではないので、新しい問題に取り組むときは、まずそれを合理的なアルゴリズムに変換するという作業が必要です。
アルゴリズムが一旦できてしまえば、その手順構造をそのまま反映した形のプログラムが書ければ便利です。この方向を追求したのがAlgolと呼ばれるプログラミング言語でした。これは、PascalとCの親に当たる言語です。そんなわけで、Algolの考え方はこれ以後のプログラミング言語のあり方を強く規定する結果となっています。ForthはAlgol系ではありませんが、それなりに、アルゴリズム構造を反映したプログラムが書けるようになっています。構造化プログラミングとか、構造化言語などと呼ばれたりします。
まえにもちょっと書いたように、同じ作業をする場合でも、アルゴリズムは一つではありません。上の例のような単純な場合でさえ、"5未満:"、"5以上10未満:"、"10以上15未満:"、"15以上:"という場合分けをすることができます。このような構造をそのまま反映すれば全く見た目の違うプログラムができるでしょう。実際に生成されるマシンコードも違ってくるでしょう。アルゴリズムの工夫は、一方ではプログラムとして書くコードの量と複雑さ、他方では実際に機械で計算される計算量、つまり、実行の効率性や、どれぐらいメモリを使うかというメモリー効率性などを考えながら行われます。まあ、私なんかのレベルでは、まず論理的に漏れがない様にできてるかとか、とんでもない勘違いとか誤記がないかという話で手一杯なんですがね(^^;)。
プログラミング言語で書かれたコードと機械の動作との関係が明確であればある程、アルゴリズムの工夫はしやすくなります。ある意味、一番やりやすいのは機械語と一対一に対応しているアセンブリ言語であるといえます。言語が「高級」になるにしたがって、プログラムと機械の動作の対応は不確定になります。一般には、機械の動作についてなど全く気にせずとも、よいプログラムは書けると考えられているようですが、私としては、少し疑問が残ります。あまり細かく詮索する必要はないような気もしますが、漠然とでもイメージがあった方がいいように思います。まあ、あくまでも想像上の話ですが。
Mops/Forthでは、基本的な部分ではアセンブリに匹敵する程、ワードと機械の動作の対応は明快ですが、ワードをまとめて、抽象化していくにしたがって、当然ながらその対応は複雑化してきます。Mops/Forthは言語の構文上の制約が非常に少ないので、機械に密着した極端に低レベルな部分から、アプリケーションレベルのハイレベルまで、簡単に駆け上ることができるのです。通常のプログラミング言語は、あるレベルに固着されているか、よくてもあるところから上のレベルだけでしか書けないようになっているので、言語そのものが高級かどうかを頻りに気にするのが通常ですが、Mops/Forthでは、そういうことは気にするまでもないことなのです。くどいようですが ... (^^;)
最終更新:2019年01月03日 23:05