: FORTH

Forth の日本語情報がほとんどないようなので、和訳などをすることにした。

Forth の概要

Forth は軽く (実装次第では最適化されていない C と同等以上に高速)、小さく (利用可能な資源が極めて限定されるような機器にも組み込み可)、それでいて非常に高い自己拡張性を持つ言語である (素朴な仕組みで、再帰、高階関数、クロージャ、定義書き換えマクロやオブジェクト指向なども実現してしまっている (Forth で実験的に記述された BASIC、C、Scheme はもちろん、代数的データ型とパターンマッチングの実装例すらある); Forth 使い的には、それらはあまり使わないようだけれども)。一方欠点としては、プログラマーの「想像力」に対する要求が高く (データの内部構造が丸見えなので (と言うか、想像力なしではそれしか見えない)、取り扱っているデータの抽象的構造を常に念頭に置いていないとたやすく「迷子」になる)、また C ですら甘いと言えるほどに「危険」である (システム用スタックもプログラマーに公開されており、そしてもちろんその増減をミスると即暴走する; おそらく、Forth は最も簡易に暴走プログラムを作れる言語である (が、「知らないうちに」作って「しまう」ような言語ではもちろんない。また、そうした暴走に通じる要素に対するチェックが非常に厳しい処理系も在る))。

にもかかわらず、「寂れている」と言えるほどに、少なくとも一般には人気がない (そのくせ、なぜか Forth に関する古書はべらぼうに高い値段が付いているが)。人によっては、「既に絶滅した古代言語」とすら言う人もいるほどである (見えないところでは意外に身近に今なお現役で大活躍してはいる。例えば今人気の仮想マシンは、どれもスタックベースであるがゆえに、スタックベース言語の総本家とも言える Forth の多大な影響下にある。また、いくつかの OS のブート言語は Forth の方言だったりする。極端な話、すべてのプログラムは論理的には構文木とその操作に帰結するがゆえに Lisp ベースである、とも言えるのと同じく、その構文木とネイティブコードとを接続するのはたいてい何らかの仮想マシンであるがゆえに、すべてのプログラムは具体的には Forth ベースである、とも言えなくはない)。私自身も、別に今さら流行らそうとはまったく思っていないが、かつては C や Lisp とも並び称された Forth の日本語資料が皆無に等しいのはさすがに日本にとってはマイナスであろう、と思い、このページを作ることを思い立ったのであった (ただ、実際に改めて調べるうちに、そのミニマムな「凄み」がツボにハマったので (ANS/ISO 規格化された際にメタプログラミング機能が大幅に強化されたようだ)、私の一番好きな言語になりそうではある (最終的には、一度はほかのスタックベース系言語を総覧した上で、そのうちのどれかから選ぶことになるだろう))。

初心者用言語としても、私は案外に Forth は有効なのではないか、と思う。コンピュータというものが具体的には何をしているのか、が Forth ではスケルトンのように丸見えなので、何ができて何が無理かが直感的に分かりやすく、またもっぱら関数系言語で展開されている高級で複雑な技法や理論も同様に「具体的動作」の観点から、素朴な仕組みの形で理解できるようになるのではないだろうか。Forth 自身の作法としては、いわゆる「カリー化」が知らずに身に付く、という恩恵もある (Forth における「定義の分割」は、歴史的には「因数分解 (factoring)」と呼ばれているが、本質的にはカリー化のバリエーションのように思われる。実際、スタックベース系言語の中には、この点に着目して Forth に関数型プログラミングを導入した形のものもある)。ともあれ、プログラミングの過程で誰もがいずれはミニ仮想言語めいたものを作るであろうことはまず確実なのであれば、Forth の学習はやっておけば後々決して無駄になるようなものではない、とは言えるだろう。

基本的情報源

ForthFreek / Forth Interest Group (FIG)

英語では、この 2 つをおさえておけばとりあえずほとんどの Forth 関連情報 (もちろんすべて英語) にアクセスできる。ちなみに後者の FIG が、世界における Forth の (少なくともユーザーサイドの) 総本山である。左の括弧の意味は、Forth には商用処理系もその早期から多く (と言うか、言語の作者自身が率先して商用 Forth のための企業を設立している)、しばしば FIG と拮抗する形で、標準化におけるもう一方の雄となっているからだ。実際、ANS Forth に至るまでの各標準や規格は、この長きにわたる綱引きのために相互に非互換な部分をいくらか持ってしまっている。なお、このページの最初に触れているように、日本語でこれらに相当するようなまとめサイトはおそらくにまだない。

Gforth マニュアル

現在、入門部まで和訳

Forth の言語仕様と初期の代表的な実装および最適化技法は、アセンブラによる記述にかなり特化されたものであり、そのために ANSI レベルの C や Java による実装は一筋縄ではいかない (余計な回り道を大幅に強いられ、普通に素直に実装しても、サイズはともかく速度が大幅に低下してしまう)。Gforth は、GCC の拡張機能によりこれを突破したもので、ANSI 標準からは逸脱するものの、C で記述されバイトコードを吐くタイプでは最速だと思われる (ちなみに JIT コンパイルはしてい「ない」)。ANSI C に準拠した記述でこれと同等以上に速いものはまず間違いなくネイティブコードを直接吐くタイプだが、それだとはっきり言ってアセンブラで直接記述した方が無駄がない分よりコンパクトになるのではないかと思う。いずれにせよ、当然それらでは移植性が犠牲になる (この点でもおそらく、アセンブラによる記述の方が、より素直な分だけ移植しやすそうである)。つまり、Gforth はまた、現時点で最も移植性が高い処理系だと思われる (ちなみに、自動インストール型の Windows バイナリ版も用意されており、Unix 系追加環境なしの素の Windows にもこれだけをインストールして使えるようになっている (コンソールは同梱の bash.exe を内包する形になっており、このため Windows 版でも ANSI エスケープが普通に使える)。ついでに書いておけば、Gforth は Win32Forth (こちらはアセンブラ記述の模様) の倍近く速い。それが持っているような GUI ライブラリは (現時点では) まったくないけれども。ちなみに、一応 Windows 用ライブラリも既に在って Gforth 並みに高速な SP-Forth という処理系もあるが、こちらは x86 専用ネイティブコードを吐くタイプである)。

さらに、シェルスクリプトとして使うための措置も、おそらくは最も (あるいはほぼ唯一) 考慮されている処理系である。と言うのは、Forth はパソコン以外の機器への組み込みをも踏まえて設計・実装されるケースがむしろ普通であり、その場合 OS すら使えない場合も普通にあるので、その存在を前提とするコードが組めないのである、つまり、たいていの Forth 処理系では、コマンドラインへの対応度は普通かなり低い。Gforth の場合はしかし、GCC が存在する以上は当然 OS も必ず存在するはずなのだから、それで、コマンドラインへの対応度が際だって高いのである。

ただし、現行の Gforth は、アプリケーション組み込み用のインタフェースがまだ試行錯誤中なのかドキュメントも用意されておらず、その目的には明らかに向かない (と言うか、そもそも Forth はアプリケーション組み込みのスクリプトエンジンとして使うには危険過ぎる言語だとは思うが。Gforth がその部分にあまり力を入れていないのも、そのことを懸念しているためかもしれない。なおちなみに、アプリ組み込み可を売りとする Forth 処理系は、たいていは Forth には非本来的な、かなり厳重なエラーチェック機能をあらかじめ組み込んでいるようだ (当然、その分速度が落ち、サイズもかさむ))。

以上を踏まえて、私は「最も標準的な実装」として紹介するには Gforth が最もふさわしい、と判断した。ただし無論、Forth には商用も含めて Lisp/Scheme に負けないほどにさまざまな処理系があり、利用者のニーズ次第では、Gforth よりもっと最適と見なせるものもその中にいくつか発見できようが、そのような比較検討の際の「基準」として、Gforth を一通り知っておくことは無駄にはならないであろうと思う。

なお、Gforth が UTF-8 対応になるのは 0.7 からで、Debian/Lenny の版ではまだ対応していない (ただし、UTF-8 対応自体は次期標準である Forth200x の草案中に含まれており (XCHAR wordset)、そこで参考実装も入手できる)。

その他の処理系

いくつか、興味深いと思われる処理系を紹介しておく。

Reva

ANS などの標準とは一線を画し、Windows/x86Linux に特化する形で Forth を再構築した、RetroForth に始まるいわゆるミニマリスト Forth の一雄。パブリックドメイン。コンパクト (約 30KB) ながら、商用処理系と並ぶ (すなわち GCC にも匹敵する) 高速さを誇る (ただし、あくまでバイトコードインタプリタなので、Forth で記述されたいわゆる高水準ワードを積み重ねると速度はかなり低下する、つまり、大規模で複雑なプログラムには向かない)。さらに、デフォルトで豊富なライブラリが付属しており、例えば gl、glu、glut、sdl、iconv、regex などのバインディングラリブラリが Windows 用の本体 DLL ともども同梱されている (Reva Forth 記述で Win/Linux 両対応)。

ANS Forth 標準は、元々は商用処理系間の標準として定められたものであるためか、いわゆる個人的な処理系としてそれに依拠したものを作るのはけっこう荷が重いようだ。それで、「高速でライブラリも豊富」となると、非商用処理系ではこの Reva ぐらいしか見当たらない (低速でよいならよりどりみどりなのだが)。ちなみに商用では、上のベンチマークにも出てくる VFX Forth (こちらは ANS Forth 完全準拠) の、Linux では無料ダウンロードできる評価版が、カーネルソース、クロスコンパイラ、PowerNet (WEB サーバー) とサポートがない程度で無期限に利用できるのでよいと思われる (Windows 版は、さらに販促画面と、EXE を生成できない制限が付くので、評価版の価値はあまりない)。なお、 SwiftForth と IForth も商用処理系である (前者は Forth の作者自身が興した会社のもの。後者も古来から有名な処理系)。

後日、デフォルトでは Windows でのコンソール入出力がうまく動かないことに気づく。起動時に強制的に UTF8 コードページに移行している (src/corewin.asm の 107-110) ので、そこをコメントアウトすれば日本語の表示はできるようにはなる。が、コンソールからの入力の方の、マルチバイト文字の各 1 バイトしか入力できない、という問題はちょっと厄介だ。これは、Win32API において、ReadConsole であれ ReadFile であれ、StdIn からは「文字単位」で取り込まれるのに対し、Reva 側ではそれらが単一バイトを返すと想定してしまっているためである (ちなみに、ReadConsole だと UTF16 に変換されるらしく (この場合下位バイトが読める)、CP932 のまま読み込むなら ReadFile の方を使う必要がある (この場合は上位バイトが読める)。すなわち、src/corewin.asm 315 行目をコメントアウトし、316 行目のコメントを外す)。この理由から、普通の C ライブラリを経由している Linux 側での UTF8 日本語入出力には何の問題もないが、Windows 側では日本語に限らず UTF8 も含めてマルチバイト文字すべてが全滅なのではないか、と思う (マルチバイトコードページのコンソールからバイト単位で読み込める関数は Win32API には存在しない)。

だから、ReadFile に貸与するバッファを 2 バイト長にして、先頭が CP932 の 2 バイト文字 1 文字目ならバッファの 2 バイト目も取り込むようにすればよいのだが (おそらく、src/revacore.asm 827-886、query サブルーチン内のどこか)、他ならぬこの問題で初めて 8086 系マシンコードと Win32API をちょっとかじった程度の私には荷が重い (マシン語は MSX の Z80A、システムレベルの GUI コールは Macintosh のツールボックス以来久しぶり)。Reva も含めてミニマリスト Forth はみな FASM というアセンブラに依存しているのだが、これは、デフォルトでサポートしている共有ライブラリ関数の簡易な呼び出しと、構造データを定義できるマクロによるところが大きいようだ (Reva の Mac 版がないのもこのため、つまり FASM 自体がまだ未対応なためだろう (Reva の作者自身は着々と NASM への移植を進めており、こちらが完成すれば、その NASM がサポートする Mac にも対応できるようになるようだ)。ちなみに、RetroForth 自身は今は C による記述へ移行しているが、こちらは現在 Windows 版がない (.NET 用や AIR 用 VM はソースの方にあるが))。こんなわけで、私自身による日本語版 Windows 対応化は、その FASM のマニュアルを訳してから、ということになるだろう (むろん、それ以前に Gforth および ANS Forth の最低限の和訳が優先される)。なお、この問題のほかにも、「空白を含むファイル名を認識できない」という割とよくある制限があるので、(Forth なら解決は Forth 上ででもできそうだが)、少なくとも Forth 習得中は、いずれにおいても問題ない Gforth の方をお勧めする (Win32API など、GUI の方で直接入出力すれば Reva でも使えなくはないとは思うが)。

あとそう言えば、ついでではあるが、少なくともフリーの処理系では、Windows での日本語まわりは Gforth 以外はほぼ全滅の様子らしいことも報告しておこう。Win32Forth は付属のエディタや IDE、GUI コンソールで日本語が使えず、SP-Forth や Aztec Forth は日本語を読ませると不安定になり、BigForth は表示がメチャクチャになる。これら以外は、Gforth の方がよいと思えたので試していない (一般に、C で記述され C のプログラムに組み込めるタイプは、C とのやり取りで遠回りを強いられるせいか、Gforth よりはるかに遅いのが普通である)。

FreeForth

Reva と同じくミニマリスト系でパブリックドメインな Forth だが、こちらはネイティブコードへコンパイルするので、プログラムの規模や複雑さが増大してもおそらくに速度は落ちない。ただ、Forth 方言としては極北とも言えるほどほかの Forth とは異質なので、一般的にはそんなには勧められない (Reva と異なり、ライブラリも現状では皆無である。DLL を含む共有ライブラリへはアクセスできるので、ライブラリ作成は困難ではないとは思うが)。私自身は、自分用の Forth としてはいくつかの理由からもっぱらこれに取り組むことになると思うが (むろん、ネット上のプログラムソースの大半は ANS Forth 用なので、やはりそちらの習得と紹介が優先されるけれども)、一般的には、プログラムの規模や複雑さが大でありながら高速を欲する場合は、ANS Forth に準拠しつつもネイティブコードコンパイラである SP-Forth や BigForth の方をお勧めする (それほどになれば当然 GUI を使うだろう、という前提で。なお、前者はロシア製、後者はドイツ製で、マニュアルがまだ完全には英語化されていない)。

なお、こちらは Windows コンソールでの日本語の表示は (たぶん) 問題ないが、入力はデフォルトではうまくいかないことが確認されている、つまり、アセンブラソースにいくらか手を入れて FASM で再アセンブルする必要がある。

Retro / retroImage を読む

Reva と FreeForth は Retro の Ver7 をベースにしているようだが、当の Retro はさらにミニマリスト指向をひた走って、ついにはこの Ver10 において Scheme をも思わせる「美しい」言語と実装の境地へと至ったようだ (実際、私は Gforth を通して ANS Forth の仕様を眺めるに、どこかしら Common Lisp を思わせる実務的で (Forth としては) ちょっと大袈裟な仕様だと感じ、そこから翻って、Scheme のような Forth も可能なのではないかと思っていた)。本家サイトのデフォルトは、言語仕様とその参考実装かと思わせるほど「簡素」だが (倍精度整数も浮動小数点数も共有ライブラリ呼び出しもないばかりか、ファイル I/O すらない)、私は非常に気に入った。たぶんそのうちに、できる限り汎用化された外部ライブラリ呼び出し機能とファイル I/O 機能を組み込んだ (Reva のライブラリなどを見る限り、倍精度整数と浮動小数点数機能は Forth で記述してしまえる模様)、Ngaro 仮想マシンの Java 実装を作ることになるだろう (あと、余力があれば FASM か TinyC による x386 Linux/Windows 専用の高速でコンパクトなネイティブ実装も; FreeForth あたりを Ngaro 化すればよいのだから、そんなに難しくはない)。

なお、ソースの方に C# 実装があるので、Windows ではとりあえずこれが使えるはずである (むろん、.NET ライブラリの呼び出し機能はデフォルト版にはない、というか、デフォルトの Java 版ともども、ひときわ簡素な (と言うかはっきり言って手抜きな) 実装である; マイ拡張用の叩き台としてはほどよい程度だが)。あとちなみに、なぜか開発版の方に Elisp 実装もあったりする。

応急措置的に、オリジナルのコンソール版 Ngaro の Windows 特化版を作成。TinyC だけでビルド可。VC++ の conio を流用したものなので、どの Windows でもビルドできるはずと思うが、一応ビルド済みの EXE ファイル (とオリジナルの retroImage) も同梱してある。いくらか機能を削っているので、コンソール画面を制御するワードなどには不具合があるかもしれないが、代わりに日本語が通る。

さらに、Java 版 Ngaro も作成。元々オリジナルのソース版にはメンテ放棄された古い Java 版が一応付属していたが、それをベースに、高速化し (C によるコンソール版よりちょっと速くなった) また Windows にも対応させた (要 jLine)。また、C によるコンソール版の機能もデバッグやプロファイル系以外は取り込んだ。バックスペースには、オリジナルのほかの版と同様にうまく対応できていないが (Linux でも少なくとも GUI コンソールでは、C によるコンソール版でもバックスペースはうまく動かない)、ブロックエディタがちゃんと動くようになっている (Linux の GUI コンソールではこちらも C によるコンソール版でちゃんと動作していない)。それと、日本語も通る (8 ビットクリーンにしてあるだけなので、UTF-8 環境では UTF-8 バイトシーケンス、シフト JIS 環境ではシフト JIS バイトシーケンスのまま入力出力される)。なお、元々実装例として利用するために 1 ファイルのみから成るように作成してあるので、Java 呼び出しや GUI の拡張はしてないし、今後もする予定はない (Retro のポータブルでない部分をよりポータブルなものに変更し、また文字は内部 UTF-32 とする Metro という派生を考えており (これらの変更には retroImage の一部修正も必要なため、デフォルトの retroImage とは互換性がなくなるので、派生化はやむをえない)、そうした拡張はその Metro の方で行う予定である)。

ほかに書くところもないのでとりあえずここに書いておくが、仮想スタックマシンから P-マシンの Pascal を調べているうちに、興味深いテキストエディタを見つけた。Textadept がそれで、最小限のコアが C で記述されている以外はすべて Lua で記述されていて、Lua でさらに好きなように拡張できる、というものだ (Lua は、Pascal 系のスクリプト言語)。実際、core/file_io.lua の try_encodings という配列を次のように変えたら、再コンパイルなしの再起動だけで認識可能エンコーディングを拡張できた。

try_encodings = {
  'ISO-2022-JP',
  'UTF-8',
  'CP932',
  'EUC-JP',
  'ASCII',
  'ISO-8859-1',
  'MacRoman'
}

少なくとも Linux 側では、この設定でこれらのエンコーディングの日本語ファイルを自動認識できた (ISO-2022-JP は UTF-8 としても解釈可能なので、必ず UTF-8 の前に置くこと。ちなみに、内部エンコーディングは UTF-8 である)。iconv と Gtk+ を要するようなので、Windows でも使えるかどうかはまだ不明ではある (一応 Windows 用のバイナリはある)。

Textadept の UI を日本語化。検索ボックス内のラベル文字列が textadept.c 内で定義されているためにバイナリの更新も要するので、Windows 用バイナリも付けてある (一応 Linux 版も付けてある; Linux ならどのディストリでも同じバイナリが使えるらしい)。なお、まだ機能の一部が具体的に何をどうするものなのか理解できていないのもあるので、あくまで暫定版である。ほか、メニューへの日本語エンコーディングの追加とトグルメニューの状態をステータスバーに表示する改良付き。

Windows では、Gtk+ の最新の Stable (これを書いている時点では 2.16.6) だと (Textadept 以外も含めて) 起動があまりにも遅いので、こちらの (2.12.9 の Revision 2) を使う方が断然よい。なお、Windows 版では、最大化して終了してから再起動した際に画面から一部はみ出してしまうので、デフォルトで画面左上 0:0 の位置に配置されるように変えてある (実際には手抜きで、起動時に必ず最大化するようにしてある (デフォルトセッションが読み込まれる際に、サイズは終了時のそれに改めて変更されるが、位置は変わらない)。現状、textadept.c の再コンパイルなしにこれを変える手段はない)。

Textadept の Tips:

* Textadept は、これまで Scite を長年使ってきた作者が、自身の作成した「動的」 Scintilla をベースに、改造や拡張がより容易なようにできる限り Lua だけで実装したテキストエディタである。可能性的には Emacs や jEdit にも匹敵し (ただしまだ若いプロジェクトなので既存の機能拡張は現状ないに等しい)、しかして Lua ベースなためにはるかに簡単で起動が高速である。私的には、Scite が行ごとの検索置換しかできないのに対し、こちらは正規表現ではなく Lua パターンにはなるが、複数行に渡る (要するに改行を含む) 検索置換が可能であることが限りなく大きい。

* ユーザーホームディレクトリ (Windows では USERPROFILE 環境変数で設定。設定がない場合は通常のホームディレクトリ) の .textadept フォルダに配置するユーザー用 init.lua は、最低限次のとおり。

-- Core extension modules to load on startup.
require 'ext/keys' -- provides key command support
require 'ext/find' -- provides functionality for find/replace
require 'ext/command_entry' -- provides tab-completion for the command entry
require 'ext/mime_types' -- provides support for language detection based on
                         -- the file; loads its language-specific module if
                         -- it exists
require 'ext/pm' -- provides the dynamic browser (side pane) functionality
require 'ext/pm.buffer_browser'  -- buffer browser
require 'ext/pm.file_browser'    -- file browser
require 'ext/pm.modules_browser' -- modules browser
if not WIN32 then
  require 'ext/pm.ctags_browser' -- ctags browser
end

-- Generic modules to load on startup.
require 'textadept'

-- Core extension modules that must be loaded last.
require 'ext/menu' -- provides the menu bar
require 'ext/key_commands' -- key commands
-- The aboves are needed as long as your init.lua is a set of additions
-- to the default init.lua.

-- User Additions

User Additions より上の部分は、textadept 本体フォルダ内の init.lua にもあるが、ユーザー用 init.lua があるとロードされないので、ユーザー用 init.lua の側で (必要な限りは) 改めてロード指定しておく必要があるもの。ユーザー設定は User Additions 以下に書く。

* 検索ボックスおよび Lua コマンド入力ボックスは、実行しない場合でも ESC キーで隠せる。

* メッセージバッファに値を表示させたいときは、コマンド入力ボックスに textadept.print(値) と入力してエンター。

* 私自身による改造は、もっぱら menu.lua、events.lua、locale.conf の 3 つを部分的に修正するだけで済んだが、menu.lua で定義しても、locale.conf の対応部分も修正しておかないと反映されないことに注意。

* デフォルトのフォントは、フォントサイズも合わせて themes/light/lexer.lua 内で定義されている。日本語の場合、フォント名の前に ! を付けておかないと一字おきに文字化けすることに注意 (ちなみに、ASCII 文字しか使わない人は、この ! を外すと起動がかなり速くなる)。

* ユーザーテーマは、.textadept フォルダ内の theme というファイル内にそのテーマのフォルダパスを書いておけばいいらしい (私自身はまだ試していない)。フォルダパスの代わりにテーマ名を書くと、textadept 本体のあるフォルダ内の themes からその名前のテーマがユーザーデフォルトとして選択される。

* 日本語版では、themes 内の各テーマの buffer.lua に buffer.wrap_mode = 2 をあらかじめ記述することで、文字単位のワードラップがデフォルトになるようにしてある。1 ならワード単位、0 ならワードラップなしである。その他のユーザーデフォルト設定も、現状ではここに記述する方が多いかもしれない (init.lua では、起動時の最初のバッファに対してしか設定できない)。