メッセージ送信の比較


HOME > TIPS > オブジェクト指向の言語比較論 > オブジェクト指向の言語比較論 > メッセージ送信の比較

メッセージ送信の比較


ここでは、C++、Java、Objective-C、Smalltalk、Dylan、Ruby、Perl、Self、NewtonScript、AppleScript といった、オブジェクト指向を実現している言語が、メッセージ送信をどのように実装しているか比較してみることだ。どれがいい、悪い、とか、オブジェクト指向の理念に一番近いのはどれか、という議論は、ここでは置いておくよ。

Smalltalk と Self のソースコードと説明は sumim さんに、Dylan のソースコードと説明は 2 さんに、Ruby と NewtonScript のソースコードと説明は GNUE(鵺)さんに、Perl のソースコードと説明は terra さんに、AppleScript のソースコードと説明はかりやんさんに書いていただきました。また、全体的な構成のアドバイスを sumim さんからいただきました。感謝!

ここの説明では、メッセージ送信をする際に、次の用語を使っているんだ。

・メッセージ送信
 オブジェクトに対してメッセージを送ることだ。静的にコード中で書いたり、メッセージを実行時に指定して送ることもある。

・メッセージ
 オブジェクトに処理を依頼するために送られるものだ。メッセージを変数として取得できる言語もあるよ。

・オブジェクトに送られたメッセージを処理するものだ。メソッドをオブジェクトとして取り扱う言語もあるよ。

・関数
 実際にメッセージの処理を行うバイトコードのことだ。これを直接取り出して、処理できる言語もあるよ。メッセージ送信とは、この関数を探し出して呼び出すもの、と捉えることもできる。

これらが実際の言語に対応するかっていうと、こんな感じで考えてみたぜ。

スクリーンショット(2011-06-16 16.48.20).png

いちおう、こういうマッピングにしてみたけど、異なる言語では異なる概念を用いるんで、なかなか完全にあてはまるものを見つけることができない。比較のために無理矢理あてはめてしまったものもあるんで、納得いかないときは教えてください。

あと、メッセージを考える際には、それぞれのフレームワークと切り離して考えることができないと思うんだよ。だから、Java は Java 2 Platform の API を使うことを、Objective-C は Cocoa の Foundation フレームワークを使うことを前提にした。C++ にもメッセージ送信を拡張するようなフレームワークがあるだろうから、それを考慮に入れるとまた違ってくると思うよ。

ちなみに、私は、仕事では C と C++、家で趣味で Objective-C を使っていて、Java は昔バイトで使っていた。いまの好みは、この中では Objective-C になるんで、けっこうひいき目があるかもね。



クラスの宣言と実装


まずは、この例で使うクラスの宣言と実装だ。

C++

class A {
 public:
 void method0();
};

void A::method0() {}

Java

class A {
 public void method0() {}
}





Objective-C

@interface A : NSObject
{}
- (void)method0;
@end

@implementation A
- (void)method0 {}
@end

Smalltalk

Object subclass: #A
 instanceVariableNames: ''
 classVariableNames: ''
 poolDictionaries: ''
 category: 'Category-Name'!

!A methodsFor: 'message category'!
message1
 ^ self! !

ただし通常の GUI を介した定義時は、methodFor: 文と ! は不要。

Dylan

define class <a> (<object>)
 slot slot-0 :: <string> = "Hello";
end class <a>;

// 全ての method-0 は <a> (とそのサブクラス) の
// インスタンスを引数に取り、
// 何も返さないことを明記
define generic method-0( a :: <a> ) => ();

define method method-0( a :: <a> ) => ()
end method;

Ruby

class A
 def method0
 end
end

Perl

{ package A;
 sub new {
  # new 時に指定したクラス(package)を取得
  my $class = shift @_;
  # 格納領域とメソッドを関係付けてインスタンスとする
  return bless({}, $class);
 }
 sub method0 {}
}

厳密には、Perlはクラスを表す構文は無く、package という名前空間を応用する。bless を呼び出すことで package内のサブルーチンをメソッド化する。

ファイルに一つしか package が存在しない時は、最上位の { と } は不要

Self

globals _AddSlotsIfAbsent: (| a = () |).
a _Define: (| slot1 = (^self) |)

クラスはないのでオブジェクトを定義する。

NewtonScript

prootA := {method0: func() begin /* 処理 */ end};

オブジェクト(プロト)を作成する。

AppleScript

on A()
 script A
  property pA : 0
  on method0()
  end method0
 end script
end A

クラスがないので、代りにスクリプトオブジェクトを定義する。
ハンドラを伴わないスクリプトオブジェクトはマルチプルインスタンスをサポートできない。

通常のメッセージ送信


まず、通常のメッセージ送信を比べてみる。これは、ソースコードをコンパイルする時点で、送信するメッセージが分かっている場合だ。この場合、コード中にメッセージを直接書くことができる。いちばん普通の送り方ってことだね。違いは記述法ぐらいしかない。


C++
メンバ関数を呼び出す。

A a;
a.method0();
A* pa = new A();
pa->method0();

Java
メソッドを呼び出す。

A a = new A();
a.method0();

Objective-C

メッセージを送信する。

id a = [[A alloc] init];
[a method0];

Smalltalk

メッセージを送信する。

| a |
a := A new.
a message1

Dylan

Generic Function を呼び出す。

let a :: <a> = make(<a>);
method-0(a);
a.method-0; //これでも可

Ruby

a = A.new
a.method0

Perl

メソッド(サブルーチン)を呼び出す。

$a = new A();
$a->method0();

Self

a slot1

NewtonScript

a := {_proto: protoA};
a:method0();

AppleScript

set aA to A()
tell aA to method0() -- ハンドラを呼び出す。
method0() of aA -- でもよい。



実行時にメッセージを取得する送信


次に、メッセージを実行時に取得する場合。言語に、メッセージを直接表すことができる概念があるかどうか、ってことに注目した。あと、メッセージを文字列などから取得できるかどうかということも、重要なポイント。

C++

不可。

Java

メッセージに対応するものがない。メソッドを使って同様のことができる。

Objective-C

performSelector: を使う。

SEL sel = NSSelectorFromString(@"method0");
id a = [[A alloc] init];
[a performSelector:sel];

Smalltalk

| selector a |
selector := #message1.
a := A new.
a perform: selector

Dylan

メッセージに対応するものがない。メソッドを使って、同様のことができるが、文字列で指定することはできない。

Ruby

a.send("method0")

Perl

$a = new A();
my $msg = "method0";
$a->$msg();

Self

'slot1' sendTo: a

NewtonScript

perform(a, intern("method0"), [])

AppleScript

不可。




メソッドオブジェクトに対する、起動メッセージの送信


これは、メソッドオブジェクトを使う場合だ。まず、メッセージかメッセージの名前を指定して、メソッドオブジェクトを取得する。そして、このメソッドオブジェクトに、起動のメッセージを送ってやるんだ。そのときに、ターゲットのオブジェクトや引数も指定する。

C++

不可。

Java

Method を使う。

try {
 A a = new A();
 Class klass = a.getClass();
 Method method = klass.getMethod(
    "method0", null);
 method.invoke(a, null);
}
catch (Exception e) {
}

Objective-C

NSInvocation を使うのが、比較的近い。

Smalltalk

| method a |
a := A new.
method := A compiledMethodAt: #message1.
method valueWithReceiver: a arguments: (Array new)

Dylan

文字列を指定するメソッドオブジェクトの取得は、不可。

let a :: <a> = make(<a>);
let f = method-0;
f(a);

Ruby

method = a.method("method0")
method[]
// または
method.call

Perl

$a = new A();
my $method = \&{ ref($a) . "::method0" };
$method->( $a, 'arg1' );

ただし、無関係のクラスのインスタンスを与えても実行してしまう。

Self

message オブジェクトを使うと近いことができる。

NewtonScript

レシーバが指定できないためにメッセージ送信とは等価ではない。

imp := a.method0;
call imp with ();

AppleScript

不可。

直接的な関数の呼び出し


これは、関数を取り出して、それを直接呼び出すものだ。これを使うと、メッセージを利用しないでメソッドの処理を行うことができる。呼び出しには、関数へのポインタや、バイトコードが使われると思う。主に、パフォーマンスを上げるために使われるでしょう。



C++

メンバ関数ポインタを使う。

void (A::*imp)() = &A::method0;
A a;
(a.*imp)();

Java

バイトコードを直接取り扱うことができないので不可。

Objective-C

IMP を使う。

SEL sel = @selector(method0);
IMP imp = [A instanceMethodForSelector: sel];
id a = [[A alloc] init];
imp(a, @selector(method0));

Smalltalk

不可。

Dylan

let a :: <a> = make(<a>);
let g = find-method(method-0, list(<a>));
g(a);

Ruby

Ruby は構文木を実行するタイプのインタプリタなので不可。

Perl

my $imp = \&A::method0;
$a = new A();
$a->$imp();

ただし、無関係のクラスのインスタンスを与えても実行してしまう。

Self

不可。

NewtonScript

不可。

AppleScript

不可。