------------------------------------------------------------------ 以下の翻訳文は、Felix Winkelmann の Thoughts on Forth Programming (http://call-with-current-continuation.org/articles/forth.txt) の 翻訳であり、原著者の許可を得て公開するものです。 2021-09-01 Nobuhiko FUNATO (nfunato @ acm . org) 更新履歴: 2021-09-02(rev10): Shiro Kawaiさんにご指摘いただいた誤訳訂正/改善を反映 2021-09-02(rev09): 公開初版 ------------------------------------------------------------------ この文書は、私のForthの哲学に関する理解と、それを特に興味深いものにしている いくつかの属性を、Forthとその使い方について私が重要と思うことと一緒に記述す る試みです。これらはすべて私の個人的意見であり、ドグマと見なすべきではありま せん。私はこれまで様々なプログラミング言語を使ってきましたが、そのほとんどが 好きではありませんでした。SchemeやPrologのように非常にエレガントで強力な言語 もあると思いますが、Forthのようにプログラミングの再考を求める言語はありません。 Forthは、一人の人間や少人数のプログラマのグループに合わせて作られた、非常に 「個人主義的」なものです。その理由のほとんどは、Forthシステムを自分で実装す ることが自然で比較的簡単であること(そしてそうすべきであること)と、言語に本質 的に可鍛性があり、個人のニーズに適応することによります。したがって、私が書いて いることは、プログラミング言語であると同時にエンジニアリング哲学でもある Forthの一つの見方に過ぎません。 私の認識では、Forthプログラマには2つの考え方の流派が存在します。一方は、極端 なシンプルさ、ミニマリズム、短い定義、そして可能な限りハードウェアに近いところ まで降りていくことを好む「古典派」です。 もう一方は、他の言語に見られる機能で古典的なForthモデルを拡張し、一般的に 機能やツールを追加することで、よりコンピューティングの主流にアクセスしやすい言語 にしようと努力する「現代派」です。後者は、概してスタック、逆ポーランド記法、重い リファクタリングといったアイデアを、一般的に現代のプログラミング言語に不可欠で 望ましいと考えられているものと混ぜ合わせようとしていますが、結果として変な記法を 備えたLispのようなものになっています。私は個人的には前者の陣営に属していますが、 それは後に続く文章でより明らかになるでしょう。 まず第一に、Forthはかなり風変わりでとっぴに見えるいくつかの基本的な特性を土台と しています。それはForthがマイクロコンピュータ時代に(今日の基準では)非常に限られた 容量を提供するマシン上で誕生したことから、シンプルさとコンパクトさに根ざしている ことによります。しかし、これらの属性は、ある種の独特の利点をもたらします。 そのような特性の1つは、逆ポーランド記法の使用とローカル変数の欠如あるいは省略です。 サブルーチンやプリミティブへの引数渡しはスタック上で行われるため、コードのファクタ リング(分解)はとるに足らないものになります。式は、関数型プログラミングの「ポイント フリー」スタイルのように、文脈から取り出して新しい定義に移動することができます。 短い定義と(通常は)低いサブルーチン呼び出しオーバーヘッドを強調することは、共通の コードシーケンスを分離することをさらに示唆しています。ローカル変数の使用はこれを 難しくすることが、ローカル変数の機能を避けるべき理由です。 もう一つの便利な機能は、後から(ワードの)値を再定義しても以前の使用箇所の意味に 影響を与えない超静的な環境です。これにより、既にコンパイルされたコードの安定性が 大幅に向上し、既存のワードをシャドウイングすることが後からコードを定義するうえで 問題とならない限り、既存の定義に対する代替の名前を見つけるプレッシャーが軽減されます。 Forthはボキャブラリーを使った非常に強力な名前空間システムを提供していますが、 シャドウイングが問題にならない限り、環境を汚染しないように名前空間を分離することは 厳密には必要ではありません。 Forthは通常、対話的に動作します。基本的な read-eval-print-loop は非常にシンプル なので、すべての機能を対話的な方法でテストできます。Forthを実装する際に最初に行う ことの一つは、トップレベルインタプリタを動くようにすることです。デバイスドライバを対話的に デバッグできることを想像してみてください(それも、高レベル言語でデバッグできるように なる前に膨大な量のコードがすでに存在して動作しなければならないLispマシンの前に 座らずに)。組込みシステムのプログラミング、テスト、調査がどれだけ簡単にできるかを 垣間見ることができます。 非常に初期の段階での対話的なデバッグは、ボトムアップがForthでの設計や実装のいつもの やり方であることをほのめかしています。これは、即興やハッキングの匂いがしますが、 私たちの心は、トップダウンの方法論、事前の設計、レイヤ化されたアーキテクチャ、 最初に計画を立ててから構築を行うという理想に縛られていて、解決しようとしている 問題を完全に把握していることはめったになく、要件、期待、基礎となるシステムの知識が 事前に完全にわかっていることは単に真実では無い、という事実を無視しているだけなのです。 Forthにはメモリしかありません。メモリ内のストレージのワードサイズとバイトサイズの セル、そしてスタックです。アセンブリ言語のレベルにステップダウンするのは面倒に 聞こえるかもしれませんが、メモリレイアウトのあらゆる側面を完全に制御することが できます。あなたとマシンのアーキテクチャの間には人工的な障壁はありませんし、 フォン・ノイマン・マシンに乗っていないと思わせようとする抽象化もありません。 実際、フォン・ノイマン・マシンの上に乗っているのですから。 そして、そう認めることで、最終的に真の公開されたマシンリソースへのアクセスが 手に入るのです。これは良いことで、すべてのC言語プログラマは このレベルのアクセスが可能(そしてそれを必要としている)ですが、Forthプログラマは、 コンパイラの透過性の無い操作や、不明確な構造レイアウトや定義されていない動作の ような、邪魔になる脆い抽象化には驚かされないでしょう。言語標準に反してプログラム する場合、常にそれを解釈する人たちに翻弄されることになりますが、実装に反して プログラムする場合は、特に自分で実装を作った場合は、不明瞭な点は何もありません。 Forthには「yak-shaving」はありません: 自分とゴールとの間にあるものはすべて そこに自分で置かれてきたものです。これは、プログラミングの仕事の多くは他人の仕事が 何に使われるかを予想して彼らの失敗を回避すること(それは実際は不可能ですが)であると 教えてくれる興味深い経験です。 それは、マシンに直接アクセスできるようにしようとしているにもかかわらず誤った安全性を 感じさせ、邪魔になったときに回避策を必要とするあらゆる種類の制限を提供したり、 習得に一生かかるようなかなり難しい安全性のモデルを課している言語ではさらに悪化します。 マシンへの低レベルの直接アクセスを提供し、同時に自動メモリ管理のような高レベル環境の すべての利点を提供する一つの言語を手にすることはできません。どこかに、より多くのコード、 より多くの定型文、より脆弱で人工的な抽象化を必要とするだけの目立ったギャップがあること でしょう。私たちは両方の方法を持ちたいと思っていますし、その二分法を苦痛でなくすることを 約束する人は誰でも信じますが、それはすべて失敗しています。マシンから離れすぎて、 言語、コンパイラ、または「パラダイム」を喜ばせるためだけに何かをしてしまうからです。 ここで性能の話を持ち出す人がいることでしょう。モダンで複雑な言語は単純なインタプリタ型 言語にはできないヘビーな最適化を提供しているのだと(とはいえ、Forthインタプリタは非常に 低レベルで、コンパイルされたコードと解釈されるコードの境界は非常に薄く、多くのCPUで インタプリタは2つか3つのマシン命令で構成されていますが)。これは技術的には正しいのですが、 このような重い最適化作業が必要かどうかは議論の余地があります。あなたのコードの ほとんどは、最近作り出されているブロートウェア(bloatware)の中で実行されることは めったに(あるいは決して)ないでしょうが、実行時パフォーマンスの調査は、やや宗教的な 意味合いを持っています。私たちは、あらゆるサイクルの速度を維持しつつ、メモリ トラフィックやキャッシュミスを増加させる膨大な量のコードを気にすることはありません。 ネイティブコードコンパイラを提供する多くのForthシステムがありますが(中には非常に 洗練されたものもあります)、多くの場合これは必要なく、メモリと複雑さの点でコストが かかるだけです。高度に最適化されたコンパイラが暗示するセキュリティへの影響を無視 するとしても、ソースから生成されたコードを完全に理解できることは、満足感があり、 心強いものです。 これは、構文レベルだけでなく、抽象化レベルでのコンパクトさにつながります。ソフト ウェアを理解できないことが、プログラマが知るべきではない詳細からプログラマを守ろう とするレイヤーの増殖につながっています。オペレーティングシステム(それ自身の抽象化を 凝集したもの)はランタイムライブラリの下にあり、ランタイムライブラリはアプリケーション レベルのライブラリのベースとなり、言語レベルの抽象化の基盤となっています。 抽象化のため、あるいはそれ以上の抽象化のために、増え続ける抽象化の塔は、実際に 実行していることについての知識の不足を認めることです。これは今日では良いことだと 考えられていますが(「無知は力なり」)、実際にはデメリット以外の何物でもありません。 そもそも、オペレーティングシステムの機能やバイナリファイルフォーマット、ハード ウェアへの直接アクセスなどの低レベルのインタフェースは、一般的に見られるような 難解で禁じられたものではありません。それらのインタフェースは、(たとえあるとしても) ひどく文書化されているか、特定のシステムやアプリケーションに特有の文書化されていない 拡張機能が含まれているだけです。あるいは、インタフェースの「良い」部分はソフトウェア 担当者に任せると決めたハードウェアエンジニアが粗雑に設計しただけかもしれません。 Forthでは、理想的には2つの層を持つことになります: 手元の問題に対するプログラミング インタフェースを提供するアプリケーション層と、外部デバイスにアクセスするための プリミティブを提供するハードウェア層です。本当の芸術は、この2つの層を統合することです。 しかし、芸術とは学ぶものではなく、他の方法 - 純粋な経験や内省 - によって身につける ものです。 チャック・ムーアのようなForthプログラミングの上層部にいる人たちは、自分たちでハード ウェアを設計してForthから使いやすいインタフェースを作るようにしていますが、ツール、 言語、標準、パラダイムといった面でそれ自体の負荷が生じますから、すべての人のための ものではありません。 ムーア氏といえば、彼はForthプログラミングのために、一見自明のことのように見えても、 それを実行するために積極的な努力をしないと空虚な言葉になってしまう賢明なルールを いくつか示してくれました。 大原則は「シンプルに」です。確かに、私たちは皆それを望んでいます。しかし、なぜ これほど多くのひどく複雑なプロトコルやアーキテクチャが存在するのでしょうか? それは、誰もこのアドバイスを真剣に受け止めていないからです。結局のところ、複雑さ とはプログラマーの醍醐味、あるいは、それを極めたという感触なのです。しかし、実際に 複雑さを追求するために必要な謙虚さは、私たちの職業の中では滅多に見られないものです。 シンプルさとは、シンプルなコードを意味するだけでなく、要件、特徴、全体の機能を シンプルにすることを意味します。ベルやホイッスル、素敵なユーザーインタフェース、 目の保養、無駄なインタフェースやプロトコル、レガシーサポート、その他、必要不可欠と されているすべてのもの(実際には本当の品質の埋め合わせに過ぎないのですが)を落とす ことを意味するのです。 物事をシンプルに保つための原則には、2つの重要なコロラリーがあります。第一に、 「憶測をしない」ということ、つまり、将来の使用を想定したコードを書いたり、抽象化 したりしないことです。確信が持てないことを計画するのではなく、今持っている要件を 満たしてください。 2つ目のコロラリーは「自分でやれ」です。このアドバイスを真面目に受け止めた場合、 これは深刻な影響を及ぼします。これはまた、ソフトウェア開発者が決して恐れるべき ではなく、実際に楽しむべきものである、学ぶことの必要性を強調しています。すべてを 自分で行うことで、平凡な抽象化や、役に立たないものでいっぱいの肥大化したライブラリや、 そのコードを使っているかもしれないアプリケーションの普遍性を説明できない人による 普遍的な使用を目的として書かれたコードにはびこるバグなどを理解する必要から解放されます。 再利用は、それが慎重に疑われているという最初の兆候があるにも��かわらず、ドグマに なっています。再利用を強調するということは、資産を生産し、永続的な価値(コードベース や知的財産と呼んでいます)を創造しようとする産業界の動きを示しているにすぎません。 それは、ソフトウェアが腐っているという事実を否定する幻想です。あなたがライブラリを 再利用しているのは、通常はほんの一部に過ぎませんが、あなたは使われていない機能や 無意味な機能、特にあなたのものではないバグのためにお金を払っているのです。 そして、これは重要なポイントです: すべてを自分で行うことで、自分が制作したソフト ウェアをよりよく理解することができます。自分のニーズに合わせて、あらゆるレベルで 修正することができます。バイナリのすべてのバイトを(正しく行えば)計算できるように なります。最後にそんなことができたのはいつですか? つまり、Forthでプログラミング するということは、常に自分で実装し、「標準」を気にしないということです。昔から 言われているように "If you have seen one Forth - then you have seen one Forth. "という古いことわざがあります。そして、それは全く問題ありません。 一人のプログラマがアプリケーション全体の責任を負う時代は終わったと言われています。 私たちはチームで考え、後から来た人たちが責任を引き継ぐことを考えるべきだと言われて います。マルチメガバイトのアプリケーションや、ウェブスケールの誇大妄想の観点から 考えるなら、この考え方は適切かもしれませんが、ソフトウェアは儚く、陳腐化し、腐敗し、 最終的には腐ってしまうということを再び否定しています。この考え方は、ソフトウェア 開発に調整された理想主義的モデルから生まれたもので、開発者はレゴのピースをつなぎ 合わせる大きな機械の中の小さな歯車です。この考え方は、基本的な前提条件が変わること、 要件が変わること、そして、大量の機能を大量のコードで変更しなければならないときに、 どのプログラミング言語も現実の問題に適切に対処できないことを否定する考え方です。 私たちは、少しの再設計、1つか2つの新しいレイヤー、たくさんのユニットテストで十分で あり、いつか私たちはこのすべてをきれいにするだろうと、それはさほど悪くないふりを しています。大きな「書き換え」は、なにか考えられない、言葉にできない、弱さや失敗を 認めることです。これはまったくナンセンス、あるいはプロパガンダです。「ベスト プラクティス」に従うことは、直感を無視し、新しいアプローチでの実験を避けて、 他のみんなと同じ間違いを繰り返すことを意味します。 ここで関係してくるのは標準の問題です。Forthの標準がありますが、それをあまり深刻に 受け止めるべきではありません。標準は、あなたが作業するプラットフォーム上では最適では ないかもしれないインタフェースデザインを課しています。それは不可能なことをしよう とする試みです。つまり、ある言語の使用に関する曖昧さを、すべての可能な状況で、 すべての可能なプラットフォームで、すべての可能なアプリケーションで解決しようと いうことです。その意図はもちろん、再利用を簡単にし、移植を容易にすることです。 しかし、これについてはすでに話しました。標準は役に立つことがありますが、準拠する こと自体が目的ではありません。さらに、普遍的な標準は定義上、非常に曖昧でなければ なりません。対象となるプラットフォームが増えれば増えるほど、すべての可能性のある 実装をカバーするために、より曖昧にならざるを得ません。そうなると、C(またはC++)言語の 標準で見られる、弁護士やそのように考える人にしか理解できない信じられないほど複雑な 動作記述に終わることになります。 誰もコードを共有することができないとは言っていませんし、他人のコードを理解することを 学ぶべきではないとも言っていませんし、デザインは一匹狼にため込まれるべきだとも 言っていません。しかし、多くの場合、特定のアプリケーションやコンポーネントの主要な 設計と実装の作業を行うのは、一人のプログラマーや非常に小さなチームであることを 理解すべきです。なぜなら、プログラムには(単純なものであっても)、主題、形式的または 非公式な要件、異なる設計決定の結果の可能性などの両方に知的に深く関与すること(中断 しないようにお願いします!)が要求されるからです。大規模な設計会議のように、誰もが いつでも何をしているのかを正確に把握しているような大規模なチームで行われるのは神話です。 バグの修正やメンテナンスのために、より大きなチームに仕事を分散させることは可能かも しれませんが、その段階では、ソフトウェアはすでに、多かれ少なかれ死にかけています。 何十年もかかるかもしれませんが、一人の人間がゼロからコア機能を書き換えることが 不可能になったら、ソフトウェアは死んでいます。いいえ、理想的なのは「あ な た」が それを書いて、それを理解し、それを維持し、変更し、必要に応じて何度でも書き直し (コンパクトさを覚えていますか?)、それを棄てて別のことをするということです。 Forthは実用的な問題を解決するための言語です。LEDをON/OFFするための言語であり、 モーターを動かすための言語であり、ピクセルをフレームバッファに書き込むための言語であり、 デバイスドライバを書くための言語であり、シングルボードコンピュータ上で実行可能なものを 立ち上げるための言語です。また、データを検索したり、ソートしたり、操作したりするための 言語でもあり、必ずどうにかしてそのような仕事になってしまう単調でつらい仕事を実装する ための言語でもあります。XMLやJSONをパースしたいのであれば、確かに可能ですが、それだけ では幸せにはなれません。これは、他の無意味な抽象化の上に無意味な抽象化を書くための 言語ではありませんし、特定の問題を今日の流行りのアルゴリズムやデータフォーマットを 使って可能な限り一般的な方法で解決するライブラリを書くための言語でもありません。 仮想マシンやアセンブラ、カスタムデータフォーマットを書くためには優れたツールです。 問題をForthで実装するのが当たり前になるほど単純化し、同時に意図した目標を達成する ことができれば、あなたは正しい道を歩んでいることになります。既成概念にとらわれない 思考と多くの思索を必要としますが、その代わりに、動くのがやっとで、すぐに腐ってしまう コードや、今まで書いてきたことを後悔し、書き換えや置き換えが難しくなるほど成長した 負債となるコードの山とならないであろう何かを得ることができます。 「良い」Forthのコードを書くのは非常に難しいことです。あるいは、少なくとも私が考える 良い Forth のコードは、他の人が考える良いコードとは異なるかもしれません。Forthは 「書くだけの言語」と呼ばれてきましたが、それは、すべてが明らかになるところまでコードを 単純化するための追加の努力が必要だからに他なりません。これは芸術であり、超越の瞬間であり、 私が到達したことがあるとは言いませんが、時々、その瞬間を垣間見ることがあり、もし私が これに一生懸命取り組めば、それはとてもシンプルになり、十分に分解され、明白で明確な 名前を使って、すべてのものがうまく収まるという事実の暗示を感じます。プログラマーが たまに経験するこの瞬間です。短くてシンプルなコードが、余計な荷物を持たずに、簡単に 理解できるように、やるべきことをやってくれるのです。Forthでそれを実現するのはずっと 難しく、より多くの時間と多くの書き換えが必要になるかもしれませんが、その結果は、より 小さく、はるかにシンプルで、完全に自己完結していて、信頼できないコードによる負担が ないので、さらに満足感が得られます。あなたが、子供たちを人身御供にするMoloch神に なっている「生産性」に対する衝動を忘れたとき、品質、優雅さ、芸術と呼ばれる特別の ものを達成することができるかもしれません。それは一生かかるかもしれないし、そこまで 到達することはないかもしれないが、それでもあなたはそれのために努力すべきです。 さもなければ、あなたは機械の歯車のままで、信じられないほど複雑な「ソフトウェアスタック」 で開発し、不十分で脆い言語を使い、かろうじて一致するインタフェースをあまりにも肥大化した ライブラリのごく一部に接続し、理解できないほどの複雑さの洒落にならないツールを使って、 なぜすべてがクソなのか、なぜ実際にプログラミングを楽しんで、自分が達成したことを心から 誇りに思うことができた最後の時間は、あなたが子供の頃だったのか、と自分自身に問いかけて いることでしょう...