ここでは、C++、Java、Objective-C、Smalltalk、Dylan、Ruby、Perl、Self、NewtonScript、AppleScript といった、オブジェクト指向を実現している言語が、メッセージ送信をどのように実装しているか比較してみることだ。どれがいい、悪い、とか、オブジェクト指向の理念に一番近いのはどれか、という議論は、ここでは置いておくよ。
Smalltalk と Self のソースコードと説明は sumim さんに、Dylan のソースコードと説明は 2 さんに、Ruby と NewtonScript のソースコードと説明は GNUE(鵺)さんに、Perl のソースコードと説明は terra さんに、AppleScript のソースコードと説明はかりやんさんに書いていただきました。また、全体的な構成のアドバイスを sumim さんからいただきました。感謝!
ここの説明では、メッセージ送信をする際に、次の用語を使っているんだ。
これらが実際の言語に対応するかっていうと、こんな感じで考えてみたぜ。
| メッセージの送信 | メッセージ | メソッド | 関数 | |
| C++ | メンバ関数の呼び出し | (なし) | (なし) | メンバ関数ポインタ |
| Java | メソッドの呼び出し | (なし) | Method オブジェクト | (取得できない) |
| Objective-C | メッセージの送信 | セレクタ | (NSInvocation が近い) | IMP |
| Smalltalk | メッセージの送信 | セレクタと引数 | CompiledMethod オブジェクト | (取得できない) |
| Dylan | 総称関数の呼び出し | (なし) | <generic-function> オブジェクト | <method> オブジェクト |
| Ruby | メソッドの呼び出し | (なし) | メソッドオブジェクト | (取得できない) |
| Self | メッセージの送信 | セレクタ | method オブジェクト | (取得できない) |
| AppleScript | ハンドラの呼び出し | 識別子 | handler オブジェクト | (取得できない) |
いちおう、こういうマッピングにしてみたけど、異なる言語では異なる概念を用いるんで、なかなか完全にあてはまるものを見つけることができない。比較のために無理矢理あてはめてしまったものもあるんで、納得いかないときは教えてください。
あと、メッセージを考える際には、それぞれのフレームワークと切り離して考えることができないと思うんだよ。だから、Java は Java 2 Platform の API を使うことを、Objective-C は Cocoa の Foundation フレームワークを使うことを前提にした。C++ にもメッセージ送信を拡張するようなフレームワークがあるだろうから、それを考慮に入れるとまた違ってくると思うよ。
ちなみに、私は、仕事では C と C++、家で趣味で Objective-C を使っていて、Java は昔バイトで使っていた。いまの好みは、この中では Objective-C になるんで、けっこうひいき目があるかもね。
まずは、この例で使うクラスの宣言と実装だ。
|
C++ |
Java |
|
Objective-C |
Smalltalk ただし通常の GUI を介した定義時は、methodFor: 文と ! は不要。 |
|
Dylan |
Ruby |
|
Perl 厳密には、Perlはクラスを表す構文は無く、package という名前空間を応用する。 bless を呼び出すことで package内のサブルーチンをメソッド化する。 ファイルに一つしか package が存在しない時は、最上位の { と } は不要 |
Self クラスはないのでオブジェクトを定義する。 |
|
NewtonScript オブジェクト(プロト)を作成する。 |
AppleScript クラスがないので、代りにスクリプトオブジェクトを定義する。 |
まず、通常のメッセージ送信を比べてみる。これは、ソースコードをコンパイルする時点で、送信するメッセージが分かっている場合だ。この場合、コード中にメッセージを直接書くことができる。いちばん普通の送り方ってことだね。違いは記述法ぐらいしかない。
|
C++ メンバ関数を呼び出す。 |
Java メソッドを呼び出す。 |
|
Objective-C メッセージを送信する。 |
Smalltalk メッセージを送信する。 |
|
Dylan Generic Function を呼び出す。 |
Ruby |
|
Perl メソッド(サブルーチン)を呼び出す。 |
Self |
|
NewtonScript |
AppleScript |
次に、メッセージを実行時に取得する場合。言語に、メッセージを直接表すことができる概念があるかどうか、ってことに注目した。あと、メッセージを文字列などから取得できるかどうかということも、重要なポイント。
|
C++ 不可。 |
Java メッセージに対応するものがない。メソッドを使って同様のことができる。 |
|
Objective-C performSelector: を使う。 |
Smalltalk |
|
Dylan メッセージに対応するものがない。メソッドを使って、同様のことができるが、文字列で指定することはできない。 |
Ruby |
|
Perl |
Self |
|
NewtonScript |
AppleScript 不可。 |
これは、メソッドオブジェクトを使う場合だ。まず、メッセージかメッセージの名前を指定して、メソッドオブジェクトを取得する。そして、このメソッドオブジェクトに、起動のメッセージを送ってやるんだ。そのときに、ターゲットのオブジェクトや引数も指定する。
|
C++ 不可。 |
Java Method を使う。 |
|
Objective-C NSInvocation を使うのが、比較的近い。 |
Smalltalk |
|
Dylan 文字列を指定するメソッドオブジェクトの取得は、不可。 |
Ruby |
|
Perl ただし、無関係のクラスのインスタンスを与えても実行してしまう。 |
Self message オブジェクトを使うと近いことができる。 |
|
NewtonScript レシーバが指定できないためにメッセージ送信とは等価ではない。 |
AppleScript 不可。 |
これは、関数を取り出して、それを直接呼び出すものだ。これを使うと、メッセージを利用しないでメソッドの処理を行うことができる。呼び出しには、関数へのポインタや、バイトコードが使われると思う。主に、パフォーマンスを上げるために使われるでしょう。
|
C++ メンバ関数ポインタを使う。 |
Java バイトコードを直接取り扱うことができないので不可。 |
|
Objective-C IMP を使う。 |
Smalltalk 不可。 |
|
Dylan |
Ruby Ruby は構文木を実行するタイプのインタプリタなので不可。 |
|
Perl ただし、無関係のクラスのインスタンスを与えても実行してしまう。 |
Self 不可。 |
|
NewtonScript 不可。 |
AppleScript 不可。 |