Forthでは局所変数機能はオプショナルであって、必ずしも全てのforth環境が、それを備えているとは限らない。しかし、利用可能であれば、一般的な局所変数と同じく、ひとつのワード(サブルーチン)定義内でのみ有効で同一性を保ち、名前によって特定される変数となる。

多くの環境で、局所変数はリターンスタックを用いて実装されているようである。リターンスタックにフレームを作り、その位置と局所変数名を結びつけるのである。Mopsではレジスタを積極的に用いる。
PowerMopsでは10個程度までの局所変数が定義可能で、全てレジスタが利用される。したがって、スタックに値を多く積み込むよりも
局所変数を用いた方が高速処理が期待できる。
iMopsでは、整数は100個以上可能だが、レジスタを利用するのは初めの3つだけで、それを超えた分はリターンスタックを用いる。
また、浮動小数点数に関しては8つまでに制限されるが、すべてレジスタを利用する。

局所変数は、"Locals"と呼ばれることが多い。整数用と浮動小数点数用の区別があり、更にそれぞれに付いて、初期化の方法の違いによる区別がある。
局所変数機構は、環境毎に構文がかなり異なる場合がある。したがって、まずはforth標準規格の方式のうち、もっとも一般的と思われるものについて説明し、その後、Mopsにおける定義方法を説明する。

整数局所変数


標準forth

整数用の局所変数を定義する場合にもっとも一般的な方法は、ワード定義において{::}の対を用いることである。ワード定義の冒頭に書く場合は、スタック効果コメントのように表記することが多いが、-- より後の出力の表示部分は機能としてもコメントでしかなく、局所変数を定義するものにはならないし、構文上も必要ではない。
: aword {: p1 p2 -- out1 put2 :}  \ p1 p2が局所変数となる。out1 out2は当ワードのスタック出力個数のコメントであって局所変数ではない
上のように局所変数を定義した場合、p1 p2の初期値はスタックから取られ、該当する値はスタックからは取り除かれる。スタックコメント同様、右の方がスタックの上方である。したがって、p1 p2は入力パラメター(引数)とみなすことができる。
局所変数の値を操作する方法は、基本的にVALUEと同じである。変数名を書くことはその値をスタックに積み込むことであり、
... TO p1 \ スタックのトップから値を取り除き、p1に格納
でスタック上の値をp1に代入する。
初期値をスタックから取らない局所変数を定義する場合、| で仕切って書く
: aword1  {: p1 p2 | v1 v2 -- out :} \ p1 p2 v1 v2が局所変数になる。p1 p2は引数とみなし得る。v1 v2は変数として利用できる。
| と -- の間に書いたもの(ここでは、v1とv2)がスタックから初期値を取らない局所変数となる。"--" は構文上必要ではないし、|の左の入力局所変数は、定義しなくても良いから、{: | v1 :} でもよい。つまり、どちらかの局所変数を1つでも定義していればよいわけである。|は局所変数の種類を切り替える印になっている。初期化の方法が違うだけで、コード内での扱い方は同じである。

どちらもワード定義内のコンパイル状態で用いなければならず、定義途中実行状態に切り替えた状態で名前を呼び出しても動作は保証されない。局所変数のコンパイルを含んだワードの実行時にのみ、当の局所変数は利用されるのである。
また、局所変数の宣言は、1つのワードで一回限りでなければならない。また局所変数定義部分は途中で改行せずに一行で書き切らなければならない。局所変数宣言は、ワード定義の途中に書くこともできる。その場合は、スタック値で初期化される局所変数は、それが書かれた地点に実行が到達した際のスタック値で初期化される。そして利用できるのも、もちろん、その宣言以降である。

ところで、局所変数を実装したシステムは、局所変数を少なくとも1回16個までは定義できるようにしなければならないとされている。16個も定義できるすると、一行で書き切るという制限はかなり厳しいように思われる。もっとも、普通は最大4個もあれば足りるが。
ともあれ、局所変数が利用できるforthシステムでは特別にドキュメントなどに制限が書いていなければ、16個までは定義できると期待できることになる。

なお、局所変数名には制限がある。
":"、"["、"^"の文字で終わる名前、アルファベット以外の文字一文字だけからなる名前、は動作が保証されない。

Mops

Mopsでは中括弧{ }の対で定義する。内側にコロンは要らない。"--"以後の出力を記述する部分がコメントでしかないことはforth標準の場合と同じである。しかし、Mopsでは--は構文上必要であり、出力コメントがなくても局所変数定義の終了を標す記号として、付けなければならない。
: aMWord { p1 p2 -- out1 out2 } 
上のp1 p2はスタックからの入力となる。右の方がスタックでは上、である。このような局所変数は、Mopsでは「名前付き引数」(named parameters)と呼ぶ。
スタック値で初期化されない局所変数を定義する場合、区切りとして"|"ではなくて、バックスラッシュ"\"を用いる。
: aMword2 { p1 p2 \ v1 v2 -- out } 
とはいえ、PowerMops iMopsともに、 | も区切り文字として識別する。
Mopsでは、v1 v2のように定義された局所変数は0で初期化される。ループカウンターなどに用いられることも多いからである。

Mopsにおいても、局所変数は名前で特定され、その値がスタックに置かれる。またスタック上の値をTOで格納することもできるが、標準的なのは、VALUEの場合と同じように、"->"である。また、局所変数の値を増減させるためのワード"++>"および"-->"も定義されている。増減幅はスタックから取る。
... 1 --> p1 \ p1の値を1減らす
... 3 ++> v2 \ v2の値を3増やす
変数名については、Mopsでは、次の、浮動小数点数局所変数との区別のために整数局所変数名は冒頭に%を用いることができないことを除けば、特に制限はない。しかし、数値のような名前は混同を招くので避けるべきである。大文字小文字の区別はされない。
局所変数名が他のパブリックなワードと同じ名前であった場合には、局所変数が優先される。
局所変数宣言は1つのワードについて一回きりに限定され、さらに、PowerMops以降では、局所変数宣言は、ワード定義開始直後、実質的な内容コード記述が始まる前に置かれなければならない。標準forthでは、一定義内に一回きりという制限だけなので、定義の途中でも必要になった時点で定義できる。この点はMopsの方が制限的である。

浮動小数点数局所変数


浮動小数点数に関しては、forth標準には局所変数の規定はない。実際には多くの環境がそれを持っているようであるが、統一できるような状況ではない、ということであろう。そこで、ここでは、Mopsでの規格を説明する。

浮動小数点数の局所変数は、整数局所変数と同じ中括弧の中に混在した形で宣言する。ただし、変数名は%で始めなければならない。逆に、変数名が%で始まっていれば、小数局所変数とみなされる。
小数についても名前付き引数とその他の局所変数の区切りはバックスラッシュである。整数の局所変数と順番を前後させてもかまわないが、引数型のものに関しては、順序がみえにくくなる。小数パラメターは整数とは独立に小数スタックから取られ、最も右側がトップ、あとは順番に下方である。
: aMword3 { p1 p2 %fp1 %fp2 \ v1 v2 %fv -- out %out } \ %fp2が小数スタックのトップ、%fp1が二番目の値を取って初期化される。
使用法は整数局所変数の場合と全く同じである。加算、減算のオペレータも同じように使用できる。ただし、増減幅はもちろん小数スタックから取る。

局所変数宣言は、ひとつのワード定義に一回、その冒頭でしか許されていないので、小数と整数の両方の局所変数を使う場合は、上の例のように混ぜ書きする以外方法は無い。やや見にくい結果になるが、普通、forth系では、両方を要するようなワードを定義するのは稀である。

局所変数の利用基準


Forthプログラマーの間では、一般に、局所変数を利用すると、ひとつのワード定義が長くなりがちになり、好ましくないといわれている。簡潔で短いワード定義はスタックを利用することの副作用であるとも考えられる点からすると、スタック圧力を軽減するためのものである局所変数は、その副作用効果を減ずることになるとはいえるだろう。
しかし、定義が長たらしくなるのは、局所変数を用いること自体の効果ではない。簡潔で短い意味的まとまりを意識せず、あれこれだらだら書き連ねる態度の結果である。局所変数はそのような態度でも一定程度までは破綻しないように助ける、というに過ぎない。
部分的なまとまりがあれば、局所変数の値をスタック経由で渡すことも、また変更があるならそれをスタック経由で返して局所変数に戻すことも可能なのであるから、ワードとしての切り出しが局所変数のせいで困難になることは考えにくい。局所変数を使用しているせいでリファクタリングしにくいようにみえるコードは、そもそもリファクタリングに適さない書き方をしているところを、さらに局所変数で縫い固めているものなのではあるまいか。
局所変数そのものには、特に悪い点はないと個人的には思う。

局所変数は、スタック上にアイテムが増え過ぎ、操作が大変になり、また、コードも読みにくくなりそうな場合の救済として用いる面もある。しかし、アイテムを一時的にどかすのであれば、リターンスタックを用いる方法もある。Mopsの場合、局所変数には大規模にレジスタが割り当てられているので、比較的抵抗なく頻繁に局所変数を用いる傾向になる。しかし、レジスタを使っているからといって、いつもリターンスタックよりも実行が軽いコードになるかというと、そうでもない。というのは、局所変数用のレジスタは呼び出しの前後で保存・回復しなければならず、局所変数が多くなると、メモリーとのやり取りも多くなる。値に複数回アクセスする場合には、リターンスタックより有利になるだろうが、一回保存して取り出す、というだけならリターンスタックと大差ない。また、局所変数の実装にリターンスタック上のフレームのみを利用している環境については、効率上の優位はない。その場合、リターンスタックと比べ局所変数の有利な点といえば、変数名を用いて任意の個所で値を呼び出せること、実行フローが錯綜しているときもリターンスタックのアイテムを落とすポイントを考えなくても良いこと、くらいであろう。

結局、局所変数を用いること自体に抵抗することに意味はないと思われるが、あまり大規模に用いると、forth的な意味ではコードの可読性が落ちるのではないかとも思われる。局所変数がレジスタで実装されていて効率が良いことがわかっている場合にも、必要最小限の個数に限って用いるのが良いのかも知れない。リターンスタックやその他のメモリーフィールドで局所変数が実装されている環境なら、ごく補助的に利用するのが良いのではないかと思われる。それでも、スタックを大きく掻き回さなければいけない状態に陥りそうなときには、局所変数を用いた方がずっと良いと思われる。


次は、条件分岐


最終更新:2019年11月15日 16:18