Self
出典: フリー百科事典『ウィキペディア(Wikipedia)』
パラダイム: | マルチパラダイム: オブジェクト指向, プロトタイプ指向 |
---|---|
登場時期: | 1986年 |
設計者: | David Ungar、Randall Smith |
開発者: | David Ungar、Randall Smith、 スタンフォード大学、 サン・マイクロシステムズ |
最新リリース: | 4.3(2006年6月) |
型付け: | 動的、強い型付け |
主な処理系: | Self |
影響を受けた言語: | Smalltalk |
影響を与えた言語: | NewtonScript、JavaScript、 Io、Cel、Agora |
Self は、「プロトタイプ」の概念に基づいたオブジェクト指向プログラミング言語である。1980年代から1990年代にかけて言語設計の実験的システムとして使われていたが、2006年、Self の開発は活発に続けられており、Self言語自身で書かれた Selfバーチャルマシンを構築する Klein プロジェクトが進められ、2006年7月に最新バージョン 4.3 がリリースされた。
目次 |
[編集] 歴史
1986年、パロアルト研究所で働いていた David Ungar と Randall Smith が Self を設計した。Smalltalk-80が一般にリリースされて産業界から真剣に受け止められ始めていることから、オブジェクト指向プログラミング言語の研究をさらに進めることを目的として行われた。彼らはスタンフォード大学に移り、Self の作業を進め、1987年に最初のコンパイラを完成させた。そして、言語だけではなくSelfのシステム全体を構築することに注力することになった。
一般への最初のリリースは1990年であり、翌年には彼らチームはサン・マイクロシステムズに移り、さらに Self に関する作業を続けた。その後、何回かのリリースが行われ、1995年のバージョン 4.0 リリースで長い活動休止状態に入った。最近のバージョン 4.2 は2004年にリリースされ、Mac OS XとSolaris上で動作した。
Slef はいくつかの言語に概念的な影響を与えた。特筆すべきものとしては、アップル・ニュートンのNewtonScriptと、動的ウェブページ構築に使われるJavaScriptがある。他に、Io言語、Cel言語、Angoraなどがある。
[編集] プロトタイプベース・プログラミング
- 詳細はプロトタイプベースを参照
本来のオブジェクト指向言語は以下の双対関係に基づいている:
- クラスはオブジェクトの基本的な機能と振る舞いを定義する。
- オブジェクト・インスタンスはあるクラスの個別の具現である。
例えば、Vehicle
クラスのオブジェクトが「名前」を持ち、「運転」とか「建材を運ぶ」といった機能を持つとする。Porsche 911
が Vehicle
クラスの1つのオブジェクト(インスタンス)で、「ポルシェ 911」という「名前」を持つとする。理論的には Porsche 911
に対して「建材を運べ」というメッセージを送ることができる。
この例はオブジェクト指向のはらんでいる問題を示している。ポルシェはどう考えても建材を運ぶのには適さないが、モデル化された Vehicle
にはその機能が与えられている。より適したモデルを生成するには Vehicle
に特殊化を施したサブクラスを使えばよい。例えば、Sports Car
とか Flatbed Truck
である。この場合、Flatbed Truck
クラスのオブジェクトだけが「建材を運ぶ」という機能を持てばよい。一方、Sports Car
にその機能を与えるのは間違いであり、単に高速に運転できればよい。
このような問題がプロトタイプという考え方を生む要因となった。クラスやオブジェクトが将来どのような機能を持つようになるか確実に予測できないと、クラス階層を正しく設計することはできないのである。プログラムに機能追加が必要となるのは日常茶飯事であり、その度にシステム全体の再設計(またはリファクタリング)をしないとオブジェクトに適切な機能を与えられない[要出典]。Smalltalkのような初期のオブジェクト指向言語では、この手の問題が頻繁に生じた。システムがある程度まで成長すると、全体として硬直化が起きて、特に基本的なクラス群は相互の関連に縛られ、機能追加が困難となる。根本のクラスを簡単に変更できる方法がなければ、深刻な問題が生じる[要出典]。
Smalltalk のような動的言語では、クラスの変更によって容易にオブジェクトの振る舞いを変えられるという特徴でこれに対処できる。しかし、C++などの言語にはそのような特徴がなく、基本的なクラスに変更を加えると他のコードに影響が出てしまう。この問題を脆弱な基底クラス問題という。一般に、そのような修正は注意深く行うべきで、同じクラスに基づく他のオブジェクトに「悪い」影響が出ないようにしなければならない。ここでいう「悪い」影響は、その時どきで異なる。
Self や他のプロトタイプベース言語では、クラスとオブジェクトの双対関係が排除されている。
何らかの「クラス」に基づくオブジェクトの「インスタンス」を作るのではなく、Self では既存のオブジェクトをコピーし、それに修正を加える。従って、Porsche 911
を作るには、他の Vehicle オブジェクトをコピーし、「高速運転」メソッドを追加すればよい。コピー元となるオブジェクトを「プロトタイプ」と呼ぶ。この技法によりダイナミズムが劇的に単純化されると言われている。既存のオブジェクトがモデルとして不適切であった場合、プログラマは単にオブジェクトに修正を加えて正しい振る舞いをするようにして、それを新たなプロトタイプとして使えばよい。既存のオブジェクトを使っているコードはそのまま使うことができる。
[編集] 言語としての記述
Self のオブジェクトは「スロット」の集まりである。スロットとは、値を返すアクセスメソッドであり、スロット名の後にコロンをつければ値をセットするメソッドになる。例えば、"name" というスロットがあるとする。
myPerson name
これは name の値を返す。
myPerson name:'gizifa'
これは値をセットする。
Self は Smalltalk と同様「ブロック」を使って処理の流れを制御する。メソッドはスロット群以外にコードを持つオブジェクトであり、任意のスロットの値としてメソッドを格納できる。メソッドの持つスロットは引数や一時変数として使われる。いずれの場合も文法的には同じである。
Self ではフィールドとメソッドに区別はなく、どちらもスロットである。メッセージによるスロットアクセスで Slef の文法の大部分が説明されるため、自分自身(self)へのメッセージも多い。そのため "self" は省略できる(また、これが言語名の由来)。
[編集] 基本文法
スロットアクセスの構文は Smalltalk に似ている。3種類のメッセージを利用可能である:
- 単項
receiver slot_name
- 二項
receiver + argument
- キーワード
receiver keyword: arg1 With: arg2
どのメッセージも値を返すので、receiver や argument もメッセージ形式をとることが可能である。メッセージの後ろにピリオドをつけると、リターン値を捨てることを意味する。例えば、
'Hello, World!' print.
これは Self によるHello worldプログラムである。'
(シングルクォート)はリテラル文字列オブジェクトを意味する。その他のリテラル(即値)として、数、ブロック、汎用オブジェクトがある。
グループ化は括弧を使って明示される。明示的なグループ化をしない場合、優先順位は単項メッセージが最も高く、次に二項メッセージ(左から右にグループ化)、キーワードメッセージは最も優先順位が低い。代入にキーワードを使うとき、式にもキーワードが含まれている場合に追加の括弧が必要となる。それによって最初のキーワードメッセージセレクタを小文字から開始したり、次の部分を大文字で開始したりといった必要がなくなる。
valid: base bottom between: ligature bottom + height And: base top / scale factor.
この一文は曖昧さがなく、次のものと同じに解釈される:
valid: ((base bottom) between: ((ligature bottom) + height) And: ((base top) / (scale factor))).
Smalltalk-80 では、同じ式が次のように記述される:
valid := self base bottom between: self ligature bottom + self height and: self base top / self scale factor.
[編集] 新しいオブジェクトの生成
もう少し複雑な例として次の記述を示す:
labelWidget copy label: 'Hello, World!'.
"labelWidget" オブジェクトへの copy メッセージでコピーを作り、そのコピーの "label" スロットに "Hello, World" メッセージを格納すべくメッセージを送っている。これを使ってみると次のようになる。
(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').
この場合、(desktop activeWindow)
が最初に評価され、desktop オブジェクトが知っているウィンドウのリストからアクティブウィンドウを表すオブジェクトが返される。次に(内側から外側へ、左から右へという順で)前掲のコードが評価され labelWidget が返される。最後にそのウィジェットがアクティブウィンドウの draw スロットに送られる。
[編集] 継承/委譲
- 詳細は委譲を参照
理論上、全ての Self オブジェクトはスタンドアロンな実体である。Self にはクラスもメタクラスもない。あるオブジェクトを変更しても他には影響がないが、影響があったほうがよい場合もある。通常、オブジェクトは自身のローカルなスロットへのメッセージしか認識しないが、「親」オブジェクトを指定するスロットを持つことによって、そのオブジェクト自身が解釈できないメッセージを親オブジェクトに委譲することができる。スロット名の後ろにアスタリスクがあるものは親へのポインタとなる。このような手法で、クラスベースの言語で継承機能が担っていることを実現する。委譲によって名前空間やスコープといった機能も実装できる。
[編集] 特徴
例えば、「銀行口座」というオブジェクトを定義し、口座の残高を管理するとしよう。一般にこのようなオブジェクトには「入金」と「出金」のメソッドが作られ、それに必要なデータスロットも作られる。これはプロトタイプであるが、そのまま汎用の口座を表すオブジェクトとしても使える。
このオブジェクトのクローン「ボブの口座」を作ることで、上記オブジェクトがプロトタイプとして使われたことになる。このとき、そのオブジェクトが持つ全てのメソッドやデータがスロットとしてコピーされる。しかし、より典型的な手法は、もっと単純な traits object(特徴オブジェクト)と呼ばれるオブジェクトをつくり、そこにクラスに関連するものを含める。
この例では、「銀行口座」オブジェクトに入金や出金メソッドを備えさせるのではなく、その親オブジェクトに持たせる。このようにすると、多数の銀行口座オブジェクトのコピーを作っても、親オブジェクトの修正によって全銀行口座オブジェクトの動作を更新できる。
これはいわゆるクラスと何が違うのだろうか? 次の意味を考えてみよう:
myObject parent: someOtherObject.
これは、'parent*' スロットに適当なオブジェクトを代入することで myObject の「クラス」を実行時に動的に変更できることを意味する(アスタリスクはスロット名の一部だが、メッセージには表記されない)。継承やスコープとは違い、委譲オブジェクトは実行時に変更可能である。
[編集] スロット追加
Self のオブジェクトにはスロットを追加可能である。グラフィカルなプログラミング環境でもできるし、'_AddSlots:' プリミティブでも可能である。プリミティブは通常のキーワードメッセージと同様の構文だが、その名前は常にアンダースコアで始まる。_AddSlots プリミティブ自身は古い実装の名残りであるため、使うべきでないとされている。しかし、これを使うとコードを短縮できるため、以下ではあえて解説する。
Vehicle という単純なクラスのリファクタリングで乗用車とトラックで振る舞いを変えることを上の例で示した。Self ではこれを次のように実現できる:
_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).
'_AddSlots' プリミティブの受信者オブジェクトが明示されていないので、これは "self" に対するものである。これを対話型のプロンプトで入力すると、"self" オブジェクトに相当するのは "lobby" と呼ばれるオブジェクトである。'_AddSlots' の引数はオブジェクトであり、そのスロットが受信者オブジェクトにコピーされる。この例では、それは1つのスロットだけを持つリテラルオブジェクトである。スロット名は 'vehicle' で、その値が別のリテラルオブジェクトとなっている。"<-" という記号は、第一のスロットの値を変更するのに使われる 'vehicle:' という第二のスロットを暗に示している。
"=" は定数スロットを意味するので、'parent*' には対応する 'parent:' が無い。このリテラルオブジェクトは 'vehicle' の初期値であり、クローン作成に関するメッセージを理解できるスロットを1つもっている。完全に空のオブジェクトは、(| |) あるいは単に () で示され、メッセージを全く受け付けられない。
vehicle _AddSlots: (| name <- 'automobile'|).
これも、前と同じオブジェクトが受信者であり、'parent*' に加えて新たに 'name' と 'name:' スロットが追加されている。
_AddSlots: (| sportsCar <- vehicle copy |). sportsCar _AddSlots: (| driveToWork = (何かのコード、これがメソッドになる) |).
以前の 'vehicle' と 'sportsCar' はほとんど同じだが、ここでは後者にオリジナルが持っていなかったメソッドを伴うスロットが含まれている。メソッドを持つことができるスロットは定数スロットだけである。
_AddSlots: (| porsche911 <- sportsCar copy |). porsche911 name:'Bobs Porsche'.
新たなオブジェクト 'porsche911' は 'sportsCar' とほぼ同じだが、最後のメッセージでその 'name' スロットの値が変更されている。これらはスロットの値は異なるものの、持っているスロットは同じであることに注意されたい。
[編集] 環境
Self の特徴の1つとして、Smalltalk システムが使っていた仮想機械と同様の仕組みに基づいている点が挙げられる。つまり、Self のプログラムはC言語などとは異なり、それ単独では機能しない。常に実行環境が必要となる。このため、アプリケーションを配布するには「スナップショット」と呼ばれるメモリをセーブしたものを使用するが、これは大きくて扱いにくい[要出典]。しかし、このようになっているため、Self 環境は強力なデバッグツールを提供できる。プログラムを任意の時点で停止させ、コードや値を変更し、実行を再開させるといったことが可能である。このような「その場」での開発によって生産性が劇的に向上する[要出典]。
さらに、Self 環境はオブジェクトを素早くかつ継続的に変更することを考慮している。「クラス」設計のリファクタリングは、単に既存のメソッドを新しいオブジェクトに引っ張ってくればよいだけである。メソッドの評価のような単純な作業は、コピーを作って、メソッドをコピーに引っ張ってきて、そこで修正すればよい。他のシステムとは異なり、その新たなオブジェクトだけが新しいコードを持っており、テストするにも他に影響が発生しない。そのメソッドがうまく動いたら、それを元のオブジェクトに戻せばよい。
[編集] 性能
Self の VM(仮想機械)は C言語と比較して(一部のベンチマークで)約半分程度の性能を達成している。
これはジャストインタイムコンパイル方式によるもので、特に研究が進んでいる部分である。
[編集] ガベージコレクション
Self のガーベジコレクションは世代型であり、オブジェクトを世代で管理する。メモリ管理システムを使ってページへの書き込みを記録し、ライトバリアを保つ。この手法は性能がよいが、ある期間実行を行っていると全体ガーベジコレクションが発生し、無視できない時間を取られてしまう。
[編集] 最適化
実行時環境は選択的に呼び出し構造を平坦化させる。これは若干の性能向上をもたらすが、型情報や各種型の複数バージョンのコードをキャッシュしておく必要がある。これにより、メソッド参照回数が減らされ条件分岐やハードコードされた呼出しが挿入される。これによりC言語に近い性能が達成され、言語としての一般性は変化せず、ガーベジコレクションを備えているのである。
[編集] 関連項目
[編集] 外部リンク
- Self Home Page at Sun Microsystems
- Papers on Self from UCSB (mirror for the Sun papers page)
- Self resources at Cetus Links
- Merlin Project
- Self ported to Linux (without many optimizations)
- Automated Refactoring application on sourceforge.net, written for and in Self
- Gordon's Page on Self
- Prometheus object system on the Community Scheme Wiki
- Video demonstrating self