こんにちは、鈴木です。
Forth というプログラミング言語をご存知でしょうか。
スターウォーズでヨーダが「フォースを使うのじゃ」と言いますが、
ヨーダの言う「フォース」は「Forth」ではなく「Force」です(^^;
Forth とは
Forth とはスタック指向のプログラミング言語です。
スタック指向のスタックとは、データ構造のスタックのことです。
よくあるプログラミング言語で「1 + 2」と書くところを Forth では「1 2 +」と記述します。
逆ポーランド記法 (RPN: Reverse Polish Notation) と呼ばれる書き方ですね。
Forth の内部で「1 2 +」を実行するときには、
- スタックに 1 を積む。
- スタックに 2 を積む。
- スタックから値を 2 つ取り出し、足し合わせ、結果をスタックに積む。
という順番で処理が行われます。
ここまでは知っていたのですが、実際に Forth のプログラムを書いたことはありませんでした。
ということで、ちょっと息抜きがてら Forth のプログラムを書いてみようと思います。
処理系のインストール
メジャーな処理系はあるのでしょうか?(というより Forth 自体がマイナー?)
GNU Forth (gforth) という処理系を見つけたのでインストールしました。
Debian 系であれば以下のようにパッケージからインストールできます。
1 |
sudo apt-get install gforth |
さっそく起動してみます。
1 |
gforth |
インタプリタとして起動するので、すぐに Forth プログラムを打ち込むことができます。
まずはスタックに 1 を積みます。
1 |
1 |
次は 2 を・・。
1 |
2 |
.s でスタックの状態を確認できるようです。
1 |
.s |
このように出力されました。
1 |
.s <2> 1 2 ok |
スタックには値が 2 つあり、それは 1 と 2 ですよ、と読み取れます。
足し算します。
1 |
+ |
足し算した結果はいずこへ??
Forth では計算に使う値も計算結果もスタックに積まれます。
1 |
.s |
確認すると、1 と 2 が消えて、3 が残されています。
1 |
.s <1> 3 ok |
「+」はスタックから 2 つの値を取り出し(スタックから消える)、足し合わせた結果をスタックに積みます。
スタック操作
Forth にはスタック操作を行うためのワードがいくつも定義されています。
ワードとは?
ここで「ワード」という言葉を使いましたが、この用語には要注意です。
というのも「ワード」=「他の言語での関数」ではないからです。
少し説明すると、Forth プログラムはワードの羅列で構成されます。
「1」や「2」もワード、「+」もワードであり、「1 2 +」は 3 つのワードで構成されています。
ワードはホワイトスペースで区切れば良いため、以下のように 1 行で書くこともできます。
1 |
1 2 .s + .s |
スタック操作用ワード
まだ全部でどれだけのワードがあるのか分かっていませんが、よく使いそうなスタック操作用ワードを集めてみました。
- . : x1 --
スタックの最上位要素を取り出す。 - dup : x1 -- x1 x1
スタックの 1 つ目の値(スタックの一番上の値)を複製し、スタックに積む。 - drop : x1 --
スタックの 1 つ目の値を削除する。 - swap : x1 x2 -- x2 x1
スタックの 1 つ目と 2 つ目の値を入れ替える。 - over : x1 x2 -- x1 x2 x1
スタックの 2 つめの値をコピーし、スタックに積む。 - rot : x1 x2 x3 -- x2 x3 x1
スタックの 3 つ目の値を取り出し、スタックに積む。 - nip : x1 x2 -- x2
swap してから drop することと同じ。スタックの 2 つ目の要素を削除する。 - tuck : x1 x2 -- x2 x1 x2
swap してから over することと同じ。スタックの 1 つ目と 2 つ目の値を交換し、2 つ目の値の複製をスタックに積む。
言葉だけでは曖昧になりそうだったので、「x1 -- x1 x1」のようにスタックの状態変化も併記しました。
「--」の左が変更前、右が変更後を現します。
dup のところに「x1 -- x1 x1」と書いていますが、これはスタックに x1 がある状態で dup すると x1 x1 という状態になることを意味します。
. は「x1 --」なので、スタックに x1 がある状態で . すると x1 は無くなるということです。
少し試してみましょう。スタックに 1 2 3 と積んでから swap します。
1 |
1 2 3 swap |
スタックの状態を確認します。
1 |
.s |
1 2 3 から 1 3 2 に変わりました。
1 |
.s <3> 1 3 2 ok |
rot は 3 つ目の値を取り出し、スタックに積みます。
1 |
rot |
スタックの状態を確認します。
1 |
.s |
3 2 1 となりました。
1 |
.s <3> 3 2 1 ok |
スタック操作といわれると push と pop くらいしか無いんじゃないかと思っていましたが、色々な種類があるんですね。
独自のワードを定義する
Forth では独自のワードを定義することができます。
以下のように書きます。
1 |
: ワード名 ... ; |
例えば次のように定義しておけば、「10」の代わりに「x」と書くことができます。
1 |
: x 10 ; |
※コロンやセミコロンはホワイトスペースで区切る必要があります。(「:x 10;」のように詰めて書くとエラーになります。)
いわゆる定数の定義だけではなく、いわゆる関数の定義も行うことができます。
例としてスタックの 1 番目の要素に 1 を加える increment を定義してみます。
1 |
: increment 1 + ; |
これはスタックに 1 を積んでから + しています。
+ はスタックから 2 つの要素を取り出し、足し合わせた結果をスタックに積むため、これで意図した動作になります。
increment は次のように使うことができます。
1 |
10 increment |
.s で確認するとスタック上に 11 が積まれていることがわかります。
1 |
.s <1> 11 ok |
他にも値を 2 乗するワードや、3 乗するワードも定義してみます。
1 2 |
: square dup * ; : cube dup dup * * ; |
スタックを操作している感覚は楽しいですね。
条件分岐
ところでスタック指向の Forth で条件分岐はどうやるのでしょうか。
条件分岐は以下のように記述します。
1 2 |
if 真の場合 then if 真の場合 else 偽の場合 then |
「then ってここに書くの?」と思われたかもしれませんが、ここに書くんです。
他の言語では「if ... then ... else」という順番ですが、Forth ではこの順番で書きます。
例としてスタックの 1 つ目の値がマイナスの場合は -777、そうでなければ 777 に置き換えるワード triple-seven を定義します。
1 |
: triple-seven 0 < if -777 else 777 then ; |
プラスの値で試してみます。
1 |
123 triple-seven .s <1> 777 ok |
マイナスの値では期待通り -777 になりました。
1 |
-123 triple-seven .s <1> -777 ok |
もう一度定義を見てみましょう。
1 |
: triple-seven 0 < if -777 else 777 then ; |
順番に処理を追っていくと、次のようになります。
- スタックに 0 を積む。
- < はスタックの 1 つ目と 2 つ目の値を取り出し、比較結果をスタックに積む。
- if はスタックの 1 つ目の値を取り出し、真ならばスタックに -777、偽であれば 777 を積む。
普通の言語であれば「if 条件 真の場合 else 偽の場合」という書き方をしますが、Forth の場合は、
- 比較対象の値をスタックに積む。
- 比較を行い、結果をスタックに積む。
- if でスタックに積まれた比較結果を元に条件分岐する。
という手順になります。
慣れるまでは「演算する値はどこに書くの?」「計算結果はどこにあるの?」と迷ってしまいますが、計算に必要な値はスタックに積み、計算結果もまたスタックに積まれる、ということに慣れてしまえば非常に明確です。
参考情報
参考情報ですが、GNU Forth のサイトと日本語訳にお世話になりました。
- Gforth Manual
- GForth マニュアル(上記の日本語訳)
まとめ
ずっと昔に JVM がスタックベースだと知ったときに「いつか Forth をやろう」という気持ちが芽生えました。それからずいぶん経ってしまったのですが、少し前に YARV もまたスタック指向だと知ったり、ちょっと息抜きしたいなーと思ったりで、それならばと Forth プログラムを書き始めました。
Forth のプログラムを書いてみて、Lisp 系の言語に初めて触れたときのような新鮮さを感じました。まだまだ分からないことだらけですが、もっと詳しく知りたいと思います。スタック指向特有のテクニックもあるのでしょうか。楽しみです。
Forth を使うのじゃ!
Comments
久しぶりに FORTH の関連記事に触れ、ワクワク😃💕しました。更なる投稿を楽しみにしてます。