オブジェクト指向vs関数型プログラミングの壮絶な宗教戦争っていつしてたの……?のコメント欄で見かけた@kenokabe氏の発言に
Smalltalkというオブジェクト指向言語のソースコードの
どこにオブジェクトやらオブジェクト指向があるんですか?
どこをどうみても、非オブジェクト指向ですが。
というのがあって、おや?っと気になりました。
長くなってしまったので、忙しい人のためのまとめ
|
もとより、アラン・ケイの発案である「オブジェクト指向」、つまり(よくある「カプセル化・継承・多態」に象徴される抽象データ型のオブジェクト指向ではなく)“メッセージング”というわかりやすいメタファーを方便として用いることで、設計、実装、動作などのあらゆるレイヤーでできるかぎり遅延結合を実践するという考え
真のソフトウェア工学はまだ未来のものだ。一年とかけずに三千人以下でエンパイアステートビルを造り上げるようなものは、現在のソフトウェア工学に存在しない。彼らはソフトウェア開発にはまだ無い強力なアイデアと道具を使った。もしもソフトウェアが何らかの工学を行うとしても、大抵はアーチの発明以前(文字通りアーチづくり、アーキテクチャ以前と言える)の古代エジプトと同じようなレベルにしかならない。劣悪な知識と工具しか持たず、何十万もの奴隷を何十年も使い石に石を重ねてゆく様は、まさに今日ほとんどのソフトウェア開発で行われている事だ。
プロジェクト設計時に知っていれば良かった事がプロジェクト期間中に明らかになる事は大変良くある。今日において、より素早い開発と高い成功の鍵となるのはこのような新しい知識で行われるものだ。「エジプト的」ソフトウェアシステムでは、もしもシステムの基礎に基本的に新しい方法を採用しようとすればかなり最初からやり直さなくてはならない。これは大変な工数を要するため、一般的には行われない。
極端なアイデアを挙げると、開発システムをそれ自体のモデルにさせて、開発中に新しいアイデアとして進化させる事がある。これには UI ウィジェットのような普通のツールだけでなく、システムのメタ構造になるようなものも含まれる。インスタンスそのものはどのように作られるだろうか? 変数は実のところどうか? 簡単に様々な継承構造の記法を実現出来るか? プロトタイプがクラスよりも上手く働くかどうか決定して、プロトタイプベースの設計に移動する事が出来るだろうか?
に照らせば、Smalltalk が実現できていることはほんのまだよちよち歩きの段階で、理想に燃えるアラン・ケイがいらだち、常に不満を漏らしていたのも無理からぬことだとうなずけます。こんなものは俺の目指す「オブジェクト指向」では決してない!と。
とはいえ、@kenokabe氏がアラン・ケイのレベルで「どこをどうみても、非オブジェクト指向です」と断言しているとも考えにくく、おそらくは、「すべての言語処理は機械語の逐次実行に還元できる」程度の軽い気持ちでおっしゃられたのだろうとの想像でいます。
そんなことはさておき、実際には、「オブジェクト指向」を単なる言語機能として提供するだけの世の多くのオーソドックスな作りの言語処理系やシステムソフトウエアに比べれば、Smalltalk は、それをよく知らない人からすれば、想像のはるか斜め上を行く方法で、アラン・ケイの考え方に根ざした実装のされ方をしています。
あとでちょっと触れる Squeak や Pharo に限れば、通常は他言語で実装されることの多い仮想マシンですらメッセージングパラダイムの Smalltalk 自身で記述され(最終的には C に変換されるにしても)、そのまま動作テストやデバッグが可能なように作られているくらいです。
アラン・ケイの「メッセージングによる計算」というアイデアに基づき、ごく初期の Smalltalk-72 からその実装を手がけてきたダン・インガルスが 80年ごろに書いた文章によれば、
Smalltalkプロジェクトは私たち一人一人が持つ創造性を計算機によって支援することを目指している。このプロジェクトの源は創造的な個性と最高性能のハードウェアがともに創造する未来にある。はじめに私たちは二つのことばの研究に注力することに決めた。描写するためのことば(プログラミング言語)と対話するためのことば(ユーザーインターフェイス)だ。これはそれぞれ人間の精神と計算機とがもつ現実世界のモデルを結ぶもの、人間と計算機のコミュニケーションの仕方を融和させるものになる。Smalltalkプロジェクトは2年から4年のサイクルを追って発展してきた。このサイクルと科学的な探求の手法[*]との間には対応関係を見出すことができる:
- 今あるシステムでアプリケーションをつくる(観察する)
- その経験にしたがって、言語を設計しなおす(理論を構築する)
- 新しい設計にしたがって、新たなシステムをつくる(検証できるような仮説をたてる)
Smalltalk-80システムはこのサイクルの5周目にあたる。この記事ではこのプロジェクトをすすめる中で認識するにいたった一般的な原則をいくつか紹介しようと思う。本文中でSmalltalkプロジェクトにふれることは多くなるが、ここに紹介する原則は普遍的なものであり、他のシステムの評価や将来の開発に役立つものと考えている。
技術的なところに立ち入るまえに、まずSmalltalkプロジェクトの精神をあらわすともいえる原則から始めよう。
達人有可:システムが創造性を支援するには、それは一人の人間が完全に理解できるものでなければならない
Smalltalk というソフトウエアは、言語処理系のユーザー(通常のプログラマ)にとってはもちろん、Smalltalk という処理系それ自体の実装者にとってもメッセージングパラダイムの有効性の実験、実践の場として構築された、暫定的ながらも(70年代における)新機軸の OS としての性格を強く持っていたことが分かります。
そんな背景もあり Smalltalk は他言語での常識がそのまま通用しないことが多々あります。
よく取りざたされる大きな違いのひとつは、Smalltalk の処理系自体がオブジェクトメモリと呼ばれる(あるいはその内容を永続化した際には仮想イメージとも呼ばれる)簡易的な OODB の中に収められたオブジェクト群によって構成され、それらオブジェクトの協働によって動作している点です。
もちろん「そうでなければ Smalltalk ではない」あるいは「例外なくすべての Smalltalk がそうなっている」ということはありませんが、少なくともオリジナルの処理系のある段階からはそのように作られており、ファンとしてはそういう他には見られない特別な機構がたまらないところでもあるのです。
したがって、個人的にはオリジナルのゼロックス社謹製 Smalltalk-80 の直系の子孫である VisualWorks や Squeak、後者からフォークした Pharo といった処理系が好みで、多くの場合これらを薦めます。GNU Smalltalk や Amber Smalltalk、古くは Little Smalltalk といったファンお手製の処理系も面白いとは思いますが、オリジナルの流れをくむ処理系における“基本”をきちんと押さえておかないと、この手の変わり種の処理系のみから得られる例外的知識を Smalltalk の常識として取り違えてしまう危うさのようなものを感じてしまいます。杞憂でしょうか。
Smalltalk では、オブジェクト同士の協働により生じたオブジェクトもまたシステムを構成するオブジェクト群と区別されることなく件の OODB 内で管理され、必要なら仮想イメージ内に永続化できるようになっています。
どんなオブジェクトも永続化可能なので、仮想イメージとそれをオブジェクトメモリに読み込める仮想マシンさえあれば、オブジェクトは(GC 対象にならないかぎり)何十年でも存在し続けます。Smalltalk環境が「オブジェクトのスープ」だとか表現されたり、ときに「秘伝のタレ」「ごった煮」などと揶揄されるのはそんな理由からです。
通常の処理系や OS がソースやコマンドをファイルとして管理する「ファイルベース」に対し、「オブジェクトベース」あるいは「イメージベース」と呼ばれる Smalltalk のその仕組みは、メッセージングパラダイム(ひいてはその先にある遅延結合)の徹底と実践、他に類を見ない生産性の向上に大いに役立っています。もっともそうした特殊性ゆえに Smalltalk を理解しがたい存在と感じたり、あるいは非効率、非現実的と毛嫌いする人も多いわけですが。^^;
Smalltalk では、入力したテキストはもちろん、それをソースコードとして解析するパーザーやバイトコードへのコンパイラなどの処理系、コンパイル結果であるメソッド(実体はバイトコードを並べたバイト列)も、その実行時のプロセス(スレッド)やコンテキストですら、すべてファーストクラスのオブジェクトです。
コンパイルが終わってメソッドが生成されてしまえば、そのとき使われたソースコード(もちろんテキストオブジェクトとして存在)もメソッドというオブジェクトの属性のひとつとして扱う徹底ぶりからも、ソースが主、オブジェクトが従の通常の言語処理系との違いが際立ちます。
遅延結合を意識して作られているせいもあって、Smalltalk でのコンパイルは多くの OOPL とは異なり、クラス単位ではなくメソッドごとにインクリメンタルに行なえます。つまりあるクラスにメソッドを追加したり、既存のメソッドを修正してコンパイルしても、他のメソッドオブジェクトがその都度再コンパイルされることは(暗黙のうちにも)ありません。
さらに特筆すべき点として、過去のメソッドのソースコードはタイムスタンプが付され時系列に OODB 内に管理されていて、いつでもどの時点のソースコード(によって生成されるメソッド)にも巻き戻せるようになっています。いまでこそ Git であたりまえに行なわれていることですが、こうした機構や開発スタイルが 1970年代に普通に使われていたというのは驚きです。
もっともすべてのメソッド変更履歴をテキストオブジェクトとして OODB 内に保持するのは、メモリを湯水のように使える現在ならともかく 64KB とかで動いていた当時としては難しかったのか、ソースコードとその履歴の時系列ストリームは、それぞれ .sources もしくは .changes というテキストファイルに若干のメタデータとともにはき出されるしくみになっています。OODB 内ではチャンクの位置情報だけ保持し、現在もしくは過去のソースが必要な時だけ引っ張ってくることで、まるでソースコード履歴が OODB 内にあるかのように利用者に錯覚させる戦略をとっています。
そんなわけで、オリジナル直系であれば Smalltalk というオブジェクト指向言語処理系のソースコードは、その処理系に添付された .sources(場合によっては .sou )というテキストファイルを探してくることで(処理系を起動することなしに)読むことが可能です。たとえば、次のファイルはググって見つけた Smalltalk-80 v2.3 というかなり初期の Smalltalk処理系のソースコードです。
Scanner、Parser、Compiler などが Smalltalk処理系を構成し協働するオブジェクト群を定義するクラスです。他にも、コンパイル時の中間生成物である構文木を構成する MethodNode をはじめとする ○○Node 群も見て取れると思います。(たとえば !Compiler methodsFor: などといったメタ情報部分で検索すると、ここで興味が持たれている処理系関係のソースコードを検索しやすいと思います)。
ただし、この .sources で Smalltalk のソースを読む行為は、あくまで Smalltalk環境それ自体に興味のない人向けのゲリラ的なものです。本来 Smalltalk のソースコードは Smalltalk環境内で、メソッドを含め、すべてがオブジェクトとして OODB 管理されているメリットを最大限に活用しながら読むことが前提になっています。また、.soruces には(.changes も同様。それが .st であっても)メタ情報やチャンク記号が多く含まれ、そもそも人間に直接読ませるフォーマットにはなっていない点も、それを直接読むことを推奨しない理由となっています。
そうは言っても、Smalltalk環境内で用意されたツールを用いながらソースコードを読むには、環境内でのある種の“作法”のようなものをまず学ぶ必要があり、それなりの訓練が必要です。独学はなかなか難しいのですが、もし興味と時間と根気があれば、ぜひチャレンジしてみていただきたいとは思います。その際には、まず処理系をひとつ決めて、それ向けに書かれたチュートリアルをステップ・バイ・ステップで一通り試してみるのが結局は近道です。
仮想マシンについては通常、環境内にそのオブジェクトは存在せず、ソースコードも同梱されていません。仮想マシン開発者向け仮想イメージ(あるいはそれを構成するためのパッケージ)を手に入れる必要があります。たとえば次のページを参考にそこから情報をたぐってみてください。
古典的な Smalltalk-80 の仮想マシンの実装については、やはり Smalltalk の擬似コードで説明されたこちらの書籍の記述が参考になるかと思います。