FORWARD


これはMopsでの拡張である。標準forthにはない。おそらく、もっと古いforthにはあったのだろう。

機能としてはDEFERと同じであるが、少し静的である。動的に、あるいは何度も定義内容を変えることは予定していない。むしろ、あるワードの定義を初めから遡及的に入れ替えるという機能が重要である。もっとも、現実には、呼び出しが必要なところでは何らかの理由でまだ定義ができないワードを、もっと後に、ただし実行前に定義する、という目的のために用いる。
FORWARD aWord
このように宣言すれば、aWordは他のワードの定義で呼び出すことができる。
: callaWord  ( n1 n2 -- n )  over aWord ;
しかし、このままだと、aWordの内容は何もない。実行前に内容を定義する必要があるが、そのためには:f;fの対を用いる。
:f aWord ( n1 n2 n3 -- n )    otherWords  ;f
構文はコロン-セミコロンと同じである。ワード名はFORWARD宣言したワード名と同じでなければならない(大文字小文字は区別しない)。FORWARD宣言していないワード名をこの方法で定義しようとすると、エラーになってロードが停止される。

同じ名前で何度でも定義し直すことができる。最後にロードされた定義が、その内容になる。

CODE


アセンブリ言語でワードを定義する場合の開始ワードである。
使用法はコロンと同じであり、定義すべきワードの名前を続ける。
CODE   ( “<spaces>name” -- )
標準forthではこのワードの名前にはコロンが使われていない。Mopsでは、ワード定義を開始する諸ワードは、すべて名前がコロンで始まるようにしてある。68kMopsとiMopsでは:CODE 、PowerMopsでは、:PPC_CODE である。

アセンブリ定義は;CODEで終了する。
;CODE  ( -- )
コロン定義などの場合と違い、定義の最後にリターン(x86ではret、PPCではblr)を忘れてはならない。
PowerMopsでは定義終了ワードも対応して;PPC_CODEである。
なお、Mopsでは、アセンブリ定義内は、コンパイル状態ではなく、解釈実行状態に止まっている。(おそらく多くのforth環境でもそうで、それが、ワード名
CODEにコロンが用いられていない理由なのではないかと憶測する。)
さらに、PowerMopsでは、:PPC_CODE定義は、ファイルからロードしなくてはならず、コンソールから入力はできない(クラッシュする)。
iMopsではコンソール入力も可能である。
多くのforth環境のもつアセンブラ機能は、アセンブリ言語を解析してコンパイルする機構が組み込まれているのではなく、ワード定義による拡張によって実現されている。マシン インストラクションをコード域に1つずつ詰め込むことができるワードが定義されているのである。そのワード名がニーモニックのような名前であるからアセンブリ言語のように見えているに過ぎない、ともいえる。
PowerMopsでは、ほぼ不足なくPowerPCのマシンインストラクションが利用できる。iMopsはX86_64のごく一部(サブセット)しか、まだ定義されていない。
アセンブリ言語(ニーモニック)は当の機械のマシンインストラクションと対応しているので、当然、機械によって異なる。OPコードを表すワードも、forth環境毎に微妙に異なるようである。
この方法で定義されたワードも、通常のコロン定義ワードと全く同じように、その名前で呼び出し、実行することができる。コンパイルも含めて、識別するための標識も不要で、全く連続的に取り扱うことできる。(多分、このような言語環境は他にはほとんど無い。)
一般に、アセンブリによるコードは、速度が重要な部分について最適化するために用いられるといわれる。しかし、近年の最適化コンパイラによるコードは、
アセンブリによる書き直しをほとんど必要としない。forthの場合はスタックのデータ構造に基づいた構造的最適化が比較的易しい。自作のiMopsでさえ、
適当にワード定義をディスアセンブルして見ても、ほとんど書き換えが必要と思われる部分はなかった。確かに、ある程度複雑なコードについてなら、
レジスタを効率よく利用するなどして、最適化する余地はある。しかし、それを最適化しても、向上率は高々数パーセント程度であろう。実際には、
アセンブリ定義を用いるのは、特殊な条件のせいで特定のレジスタを指定して操作したいというような場合がほとんどである。
アプリケーション プログラムについて、アセンブリ定義が必要あるいは有用な場面さえ、ほとんど生じない。
iMopsやPowerMopsでは、アプリケーションのエントリーでスタックポインタ等の必要データを特定のレジスタに格納する場面や、ダブル セルの結果を出すため
forthワードだけでは実現しにくい演算などに、アセンブリ定義を用いている。
iMopsでのアセンブリ定義の実例:
:CODE UM*
   rDSP 8 RAX     %_LD64,
   rDSP 0         %_UMUL-M,
   RAX  rDSP 8    %_ST64,
   RDX  rDSP 0	  %_ST64,
                  %_RET,
;CODE

上の例のように、Mopsでは、アセンブリもポストフィックス(逆ポーランド型)である。被演算項は、基本、左がソース、右がターゲットである。

【宣伝】ちなみに、iMopsのコンパイラは、比較的単純なソートなどのコードでは、商用のVFXForthとiForthの間程度の実行速度が出ている(環境の違いを考えると
やや劣る感じではあるが、ほぼ同水準といえると思う)。他の高級言語では何かの言語の最適化ネイティブ コンパイラを書くのは高等技術のようだが、
forthでforthを作るのなら、正直、それほどでもないのである。

ワード定義の方針


長々しい定義を持った関数は、その意味を把握しにくく、誤りも含みやすい。定義が短ければ、内容は明瞭になって読みやすくなり、バグの発見、コードの保守にも有利である、といわれる。(リ)ファクタリングなどと、近年はいわれてきた。古くから、forthではワードを細分して定義することが推奨されてきた。通常の定義は数ワード程度でできているのが望ましいとされる。これは、スタックに値を溜め込まないように手順を考えることによって、かなりの程度実現できる。

しかし、実際には、あまりに細かく分割すると、通常は、実行速度上もスペース上も不利である。そればかりか、読みやすくなるとも限らない。というのは、長たらしくとも基本的なワードを使った処理が1つの定義内にあれば、その内容を順を追ってたどっていくこともできる。しかし、ユーザー定義のワードが呼び出されていれば、結局はその定義をみなければ内容がわからず、その定義が散在していたりすると、ソースコードのあちらこちらを参照しつつ、そこで組み合わせて考える必要がでてくる。これでは定義を細分したせいで却って大変ということもあり得るからである。

例えば、1つ2つの四則演算を独立のワードにする意味はほとんどない。細かければ良いというものではない。重要なのは、"そのワードが何をするのか"を、計算手順それ自体としてではなく、機能的な意味として把握することである。ワードの分割とは機能の分割であり、分割後の単位もまた機能として把握できなければならない。そして、それぞれの機能単位には、その機能の意味内容を普通の言葉として最も明確かつ平易に記述する簡潔な単語を、その名前として与えれば良い。それが実現できれば、各ワードが何をするのか、その名前から明確にわかるため、コードも読みやすいものになるだろう。単位が機能的にまとまっていれば、再利用の可能性も高まる。加えて、インタラクティブな開発環境であるforthでは、各ワードを1つ1つ実行して試してみることができる。つまり、最小単位をなすワードからそれらを複数複合したワードへと段階を追って、間違いや予定外の動作の有無を確認していくこともできるわけである。定義内容が短ければ、コードも読みやすく、間違いもまた発見しやすい。最小単位が正確に定義され、それらの組み合わせが正確であれば、最終的なプログラムもまた正しく動作するであろう。

というのが、forthの基本理念であり、ある種、理想化されたforthプログラミングのテーゼであると思う。
まあ、それは理想である。それほど簡単にはいかないが、それでも、考え方としては、機能的分割を頭の片隅においておくと良いのではないかと思う。小さく基本的な機能を組み合わせて、抽象的あるいは複雑な機能に高めていく、というのがforthの方針だと思われる。

実際、スタックで値を受け渡すことを意識すると、長たらしいワード定義を書いているときでも、「今ここで何をしているのか」と考えて、可能なところまで進んでから、「さて次は何をするのか」と考えることが多い。その結果、長いワード定義の中の、この部分は何をしていて、この部分は何をしていて、と部分部分が区分けできる結果になっていることが多い。そういうときには、その部分部分をワードとして切り出して分割すればよいということになるが、切り出される処理自体に汎用性が無く、他からも呼び出されそうなものでもないときに、わざわざ切り出しても、現在のCPUでは、スペース上も実行速度上も不利になるだけのことが多いのである。そういう場合は、長たらしい定義のままにしておく方を、私自身は好む(できれば、何をしているのかコメントを付けつつ — これもついサボりがちだが —)。そこらへんは、好きずきではないかと思う。似たような手続の繰り返しが見つかれば、同じ手順にできないかを考えて、可能ならばワードとして定義し、それを呼び出すという書き直し(リファクタリング)はする。サイズが少し小さくなる場合が多い。
もっとも、MopsはもともとPC専用なので、通常のforthコアほどはサイズは小さくはないし、その必要もないのだが:リソース等を除いて500kb程度。


次は


最終更新:2013年06月15日 23:25