Forth はスタックを基本とするプログラミング言語である。 このため、他に類をほとんどみない特異な部類に属する。
WSL の Ubuntu 20.04 で動かしている。インストールは次の通りである。
$ sudo apt get install gforth
主な参照先は次のとおりである。あえて Qiita は外した。↵
以下の記述は WSL2 の Ubuntu 20.04 Gforth 0.7.3 のときのものである。
gforth を起動するとびっくりする。まず、対話環境にプロンプトがない。 それに、リターンキーを打っても次の行に行かない。 そこで、このページではキーボードで打った文字列をアンダーラインで表示することにした。 また文字列を打った後は Enter キーか Return キーを押すが、これを ↵ で表示することにした。 まずすべきことは、対話環境から抜けることである。これには bye を入力する。
$ gforth↵ Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc. Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license' Type `bye' to exit bye↵ $
gforth はスタックを基本とした演算である。スタックにはいろいろなものを入れられる。 Forth には複数のスタックがあるが、スタックはしては以下特記なき限りパラメータスタックを指すものとする。 当然、数字を入れることができる。数字の演算もできる。また文字列も表示できる。
$ gforth↵ Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc. Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license' Type `bye' to exit 1↵ ok 3↵ ok +↵ ok .↵ 4 ok ." Scarlatti"↵ Scarlatti ok bye↵
gforth が動作することを確認した。 さて、平方数の和の問題を Forth で解くにはどうすればいいか、 その考えをメモする。
gforth の環境で、平方数の和となる組を順に表示する:
sqsum 2 1 1 5 2 1 8 2 2 10 3 1 13 3 2 17 4 1 18 3 3 (中略) 997 31 6 1000 26 18 1000 30 10
さて、文字列の表示であるが、." で始まり " で終わるものが文字列であること、 この文字列が下記の手順で表示されることを理解した。
." S i j" cr ." -----"↵ S i j ----- ok
平方数の和の問題であるが、まずは 1 から始まって n で終わる階差 1 の等差数列を表示するにはどうするかを知りたい。 当面は末項の n を 7 と決め打ちする。まず、1 から 7 まで順に表示するプログラムである。
: seven 8 1 do i . cr loop ;↵ ok seven↵ 1 2 3 4 5 6 7 ok
2重ループを作ることはできるだろうか。こんな感じだ。
1 2 1 2 2 3 1 3 2 3 3 4 1 4 2 4 3 4 4 (以下省略)
実際にはこんな感じで似たことはできる。
: d-loop 4 1 do i 0 do i . j . cr loop loop ;↵ ok d-loop↵ 0 1 0 2 1 2 0 3 1 3 2 3 ok
次の目標は、指標の最小値 low = 6 と指標の最大値 high = 8 のときの、次のスタック
high ... low スタック 65 53 52
から、最小値 52 に対応する指標 6 を求めることにある。しばらく時間がかかりそうだ。
配列は、create と allot の組み合わせでできそうだ。また、配列の最小値もなんとかなりそうだ。
create a 5 cells allot↵ ok 3 a 0 cells + !↵ ok 1 a 1 cells + !↵ ok 4 a 2 cells + !↵ ok 1 a 3 cells + !↵ ok 6 a 4 cells + !↵ ok : 5min a 0 cells + ! 5 1 do a i cells + @ min loop ;↵ ok 5min↵ ok .↵ 1
ではここで、配列の最小値をとる指標(ここでは 0 から 4 まで)を得るにはどうしたらいいだろうか。 argmin という変数を新たに作ってワードを書いてみた。しかし、汚い。
variable argmin↵ ok : 5argmin 0 argmin ! 5 0 do a argmin @ cells + @ a i cells + @ > if i argmin ! then loop ;↵ ok 5argmin↵ ok argmin @ .↵ 1 ok
argmin という変数はもう p でよい。また、上限と下限は変数 low と high で渡すことにした。配列は s にした。
variable p↵ ok : argmin high @ 1+ low @ dup p ! do s p @ cells + @ s i cells + @ > if i p ! then loop ;↵ ok argmin↵ ok p ?↵ 1 ok
以下、試行錯誤の結果(何日かかったかは言うまい)、次のプログラムができた。
1 value p 1 value low 2 value high create s 256 cells allot create q 256 cells allot 2 s 1 cells + ! 5 s 2 cells + ! 1 q 1 cells + ! 1 q 2 cells + ! : sq dup * ; : sq+ sq swap sq + ; : argmin high 1+ low dup to p do s p cells + @ s i cells + @ > if i to p then loop ; : shrink low 1+ to low ; : widen high 1+ to high high sq 1+ s high cells + ! 1 q high cells + ! ; : heighten 1 q p cells + +! p q p cells + @ sq+ s p cells + ! ; : enlarge q p cells + @ 1 = if widen then heighten ; : fit q p cells + @ p = if shrink else enlarge then ; : prt s p cells + @ . p . q p cells + @ . ; : sqsum cr ." S i j" cr ." -----" begin cr prt fit argmin s p cells + @ 1000 > until ;
ここまで打って、sqsum↵ と入力すると、ずらずらと 1000 までの合計を出す。(2020-10-28)
ここでいう「記号」はワードのほか、慣習として使う文字列を含む。その場合は「非ワード」とする
スタックは鉛直に伸びるイメージがあり、重力に逆らってスタックの上に「置く」、「積む」などとよく言われる。 しかし、ここではスタックは左に伸びるものとする。スタックトップに置く(push する)とは、 目の前から左に伸びる今までのスタックを1つずつずらして左に押しやり、空いた場所に所望の値を入れることをいう。 使う(pop)するときは一番右を取り除くものとする。 ワードは大文字と小文字を区別しない。ここではすべて小文字にした。 意味の欄にあるトップとはスタックトップのことである。 また、トップをトップのアドレス(位置)とトップの値の両方の意味で使っている。 スタックのアイテムとは、スタックにあるものを指す。スタック効果の文字は、n, n1, n2, n3 は整数を、 d, d1, d2 は整数のペアを、b は真偽値を表している。
この二乗和問題では配列が大きな役割を担っているのだが、Forth については配列という用語を聞いたことがないのだ。 どうしてだろう? GForth のマニュアルの 3.32 に Arrays and Records(www.complang.tuwien.ac.at) (配列とレコード)という項があるので和訳してみた。
Forth には配列やレコード(C 言語でいう構造体)といったデータ構造を定義する標準的なワードはない。 しかし、アドレス算術(address arithmetic)に基づいてワードを作ることができる。 また、配列やレコードを定義するためのワードも定義できる(ワードの定義を見よ)。
ワードの定義の方法を覚えた Forth 初心者の最初の仕事は、 配列の定義(できれば n 次元の配列)である。 先に進めば、できる。私も、できた。そこから、学べる。 しかし、定義したワードがほとんど使い道がなくてもがっかりするには及ばない (不適切な利用はもっとひどくなるから)。(後略)
やはり英語であるが、Forth の総本山でこのような主張があるのも見た。
Arrays in Forth(forth.org)というページである。
初心者から受ける質問:どうして Forth には、他の言語にあるような標準、たとえば配列などがないのか? その答:Forth は必要に適した新しいデータ型を作るのが非常に容易なので、 プログラムをお仕着せの型に合わせるよりは都度新しい型を作るからだ。
悩んでいたが、二乗和問題であればスタックそのものを配列とみなすのが最善だろうという結論に達した。
しかし、そのあとで配列をスタックで実装してはいけないという主張を目にした。J.L. Bezemer 氏の著作
http://ficl.sourceforge.net/pdf/Forth_Primer.pdf
の 2.3 節に書かれている。
2.3 Deep stack manipulators There are two manipulators that can dig deeper into the stack, called ’PICK’ and ’ROLL’ but I cannot recommend them. A stack is NOT an array! So if there are some Forth-83 users out there, I can only tell you: learn Forth the proper way. Programs that have so many items on the stack are just badly written. Leo Brodie agrees with me.
If you are in ’deep’ trouble you can always use the returnstack manipulators. Check out that section.
以降、深いスタックを取り出す pick を使っている。どうやら、この pick は仲間の roll とともに、 推薦されないワードなのだ。なんということだろう。一応、pick の使い方として残しておく。
スタックに 3 1 4 1 6 という 5 つのアイテムがあるとする。このアイテムの総和を求めよう。まず、 スタックを消費する、破壊的な方法である。
: 5sum 4 0 do + loop ;↵ ok 3 1 4 1 6↵ ok 5sum↵ ok .S↵ <1> 15 ok .↵ 15 ok
5 つのアイテムを加えるが、加算は4回である。植木算を思い出す。
続いて 5 つのアイテムの最小値を求めよう。
: 5min 4 0 do min loop ;↵ ok 3 1 4 1 6↵ ok 5min↵ ok .S↵ <1> 1 ok
それでは仕切り直して、非破壊的な総和を求めよう。
: 5sum dup 6 2 do i pick + loop ;↵ ok 3 1 4 1 6↵ ok 5sum↵ ok .S↵ <6> 3 1 4 1 6 15 ok
pick を使って非破壊的にスタックにアクセスして総和を求めた。 アキュムレータの初期値は dup を使って 6 を指定しているが、開始インデックスが 2 で 境界値が 6 である。 この境界値というのが曲者で、インデックスは加算されるが、最終的には 5 で止まり、6 にはいかない。 ここもどこかすっきりしないが、値が得られたということでいいことにしよう。非破壊的な min も同様である。
: 5min dup 6 2 do i pick min loop ;↵ ok 3 1 4 1 6↵ ok 5min↵ ok .S↵ <6> 3 1 4 1 6 15 ok