Forthを使うのじゃ

Pocket

こんにちは、鈴木です。

Forth というプログラミング言語をご存知でしょうか。

スターウォーズでヨーダが「フォースを使うのじゃ」と言いますが、
ヨーダの言う「フォース」は「Forth」ではなく「Force」です(^^;

 

Forth とは

Forth とはスタック指向のプログラミング言語です。

スタック指向のスタックとは、データ構造のスタックのことです。

よくあるプログラミング言語で「1 + 2」と書くところを Forth では「1 2 +」と記述します。

逆ポーランド記法 (RPN: Reverse Polish Notation) と呼ばれる書き方ですね。

Forth の内部で「1 2 +」を実行するときには、

  1. スタックに 1 を積む。
  2. スタックに 2 を積む。
  3. スタックから値を 2 つ取り出し、足し合わせ、結果をスタックに積む。

という順番で処理が行われます。

ここまでは知っていたのですが、実際に Forth のプログラムを書いたことはありませんでした。

ということで、ちょっと息抜きがてら Forth のプログラムを書いてみようと思います。

 

処理系のインストール

メジャーな処理系はあるのでしょうか?(というより Forth 自体がマイナー?)

GNU Forth (gforth) という処理系を見つけたのでインストールしました。

Debian 系であれば以下のようにパッケージからインストールできます。

さっそく起動してみます。

インタプリタとして起動するので、すぐに Forth プログラムを打ち込むことができます。

まずはスタックに 1 を積みます。

次は 2 を・・。

.s でスタックの状態を確認できるようです。

このように出力されました。

スタックには値が 2 つあり、それは 1 と 2 ですよ、と読み取れます。

足し算します。

足し算した結果はいずこへ??

Forth では計算に使う値も計算結果もスタックに積まれます。

確認すると、1 と 2 が消えて、3 が残されています。

「+」はスタックから 2 つの値を取り出し(スタックから消える)、足し合わせた結果をスタックに積みます。

 

スタック操作

Forth にはスタック操作を行うためのワードがいくつも定義されています。

ワードとは?

ここで「ワード」という言葉を使いましたが、この用語には要注意です。

というのも「ワード」=「他の言語での関数」ではないからです。

少し説明すると、Forth プログラムはワードの羅列で構成されます。

「1」や「2」もワード、「+」もワードであり、「1 2 +」は 3 つのワードで構成されています。

ワードはホワイトスペースで区切れば良いため、以下のように 1 行で書くこともできます。

スタック操作用ワード

まだ全部でどれだけのワードがあるのか分かっていませんが、よく使いそうなスタック操作用ワードを集めてみました。

  • . : 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 2 3 から 1 3 2 に変わりました。

rot は 3 つ目の値を取り出し、スタックに積みます。

スタックの状態を確認します。

3 2 1 となりました。

スタック操作といわれると push と pop くらいしか無いんじゃないかと思っていましたが、色々な種類があるんですね。

 

独自のワードを定義する

Forth では独自のワードを定義することができます。

以下のように書きます。

例えば次のように定義しておけば、「10」の代わりに「x」と書くことができます。

※コロンやセミコロンはホワイトスペースで区切る必要があります。(「:x 10;」のように詰めて書くとエラーになります。)

いわゆる定数の定義だけではなく、いわゆる関数の定義も行うことができます。

例としてスタックの 1 番目の要素に 1 を加える increment を定義してみます。

これはスタックに 1 を積んでから + しています。

+ はスタックから 2 つの要素を取り出し、足し合わせた結果をスタックに積むため、これで意図した動作になります。

increment は次のように使うことができます。

.s で確認するとスタック上に 11 が積まれていることがわかります。

他にも値を 2 乗するワードや、3 乗するワードも定義してみます。

スタックを操作している感覚は楽しいですね。

 

条件分岐

ところでスタック指向の Forth で条件分岐はどうやるのでしょうか。

条件分岐は以下のように記述します。

「then ってここに書くの?」と思われたかもしれませんが、ここに書くんです。

他の言語では「if ... then ... else」という順番ですが、Forth ではこの順番で書きます。

例としてスタックの 1 つ目の値がマイナスの場合は -777、そうでなければ 777 に置き換えるワード triple-seven を定義します。

プラスの値で試してみます。

マイナスの値では期待通り -777 になりました。

もう一度定義を見てみましょう。

順番に処理を追っていくと、次のようになります。

  1. スタックに 0 を積む。
  2. < はスタックの 1 つ目と 2 つ目の値を取り出し、比較結果をスタックに積む。
  3. if はスタックの 1 つ目の値を取り出し、真ならばスタックに -777、偽であれば 777 を積む。

普通の言語であれば「if 条件 真の場合 else 偽の場合」という書き方をしますが、Forth の場合は、

  1. 比較対象の値をスタックに積む。
  2. 比較を行い、結果をスタックに積む。
  3.  if でスタックに積まれた比較結果を元に条件分岐する。

という手順になります。

慣れるまでは「演算する値はどこに書くの?」「計算結果はどこにあるの?」と迷ってしまいますが、計算に必要な値はスタックに積み、計算結果もまたスタックに積まれる、ということに慣れてしまえば非常に明確です。

 

参考情報

参考情報ですが、GNU Forth のサイトと日本語訳にお世話になりました。

 

まとめ

ずっと昔に JVM がスタックベースだと知ったときに「いつか Forth をやろう」という気持ちが芽生えました。それからずいぶん経ってしまったのですが、少し前に YARV もまたスタック指向だと知ったり、ちょっと息抜きしたいなーと思ったりで、それならばと Forth プログラムを書き始めました。

Forth のプログラムを書いてみて、Lisp 系の言語に初めて触れたときのような新鮮さを感じました。まだまだ分からないことだらけですが、もっと詳しく知りたいと思います。スタック指向特有のテクニックもあるのでしょうか。楽しみです。

 

Forth を使うのじゃ!

 

Pocket

Comments

  • akij.esperanto  On 2018年6月1日 at 17:52

    久しぶりに FORTH の関連記事に触れ、ワクワク😃💕しました。更なる投稿を楽しみにしてます。
     

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です