2008年04月13日 06:00 [Edit]
タイプ・クラス・プロトタイプ - OOの語彙
その違いは微妙というにはあまりに大きいので、ここでおさらい。
駄文 - JavaScript と「クラス」と「コンストラクタ」と「プロトタイプ」って言葉の定義が難しいよなあ - IT戦記JavaScript関数の実体は、Functionクラスのオブジェクトです。今回はFunctionクラスの機能を網羅的に解説します。 JavaScriptの関数オブジェクトを完璧に理解する - builder by ZDNet Japan「Function クラスのオブジェクト」って言いますよねー。
僕もそういう風に言ったりするんですけど、本当は微妙ーに違うんですよね。
Type(型)って何?
まず、タイプ=型から説明しよう。
型というのは、「どんなデータを、どう置くか」の決まり。例えばCのint a = 42;は、「aはintという型ですよ」という意味になる。intという型には、42は入っても、3.14159265は入らないので、int a = 3.14159265;はコンパイラーが弾いてしまう。
これは、静的な型(static type)を持つ言語の関数と組み合わせ考えるとわかりやすい。静的な型を持つ言語では、関数というのは、どんな型を持つデータを受け付けるかを「知って」いて、型が合わないとそれを受け付けない。strlen("foobar")はOKでもstrlen(42)はNG。
型がある言語における関数が何かといえば、「自分がどんなデータを受け付けるのかを知っているサブルーチン」ということになる。
Object(オブジェクトって何)?
オブジェクトは逆に、データの方が何ができるかを「知って」いる。[1,2,3].lengthといえば、配列[1,2,3]がその長さを「知っている」というわけだ。"foobar".lengthの"string"も長さを「知って」いる。しかし{"one":1}.lengthといえば、「lengthなんてプロパティはありません」と怒られる。{"one":1}は長さを「知らない」。
オブジェクトとは何か、といえば、「自分が何が出来るのかを知っているデータ」ということになる。
型とオブジェクトの共通点
「自分がどんなデータを受け付けるのかを知っているサブルーチン」にしても、「自分が何が出来るのかを知っているデータ」にしても、その目的は、データとプログラムの組み合わせを限定するところにその目的がある。プログラムという「動詞」の方がデータという「名詞」を限定するのが「型」で、データという「名詞」がプログラムという「動詞」を限定するのがオブジェクト、という言い方もできる。
なぜ限定するかといえば、ひとえにプログラマーの脳力を省力するため。データの種類がn個あって、それらを扱うプログラムの種類がm個あったら、可能な組み合わせは、一つのプログラムが一度に扱うデータ(=引数)がたった一つの場合でさえn*m個の組み合わせが考えられるが、「まとも」な組み合わせはそんなにない。"one"+1の正解は4だろうかそれともone1だろうか。型もオブジェクトもそういうことに悩まないために存在する。
今日日の言語は、どちらの限定法も受け付けるものが多いが、ここからは主にオブジェクトについて話をする。
Class(クラス)はオブジェクトの設計図
オブジェクトを作るには、二通りの方法がある。
- 設計図どおりに作る
- すでにあるオブジェクトをパクる
最初に普及したのが、1のやり方で、設計図はクラスと呼ばれている。クラスはあくまで設計図なのでオブジェクトではない。型がデータそのものでないのと同じように。
Prototype(プロトタイプ)はオブジェクトの母
ところが、JavaScriptのおかげで2のやり方が注目されるようになった。すでにオブジェクトがあるなら、それを「コピー」して、違うところだけ後付すればいい。このコピーの元をプロトタイプと言う。母となるオブジェクトから子となるオブジェクトを作るというわけだ。
このやり方でオブジェクトを作る言語を、プロトタイプ型オブジェクト指向言語(prototypal object-oriented language)とかプロトタイプベース(prototype-base OO)と呼んでいる。それと区別する意味で、クラスを元にオブジェクトを作る言語はクラスベース(class-base OO)と呼ぶようになった。
何が決定的に違うか
クラスベースのOOとプロトタイプベースのOOで決定的に違うのは、プログラムを動かしている最中にオブジェクトが出来ること、すなわちメソッド(method)を追加したり再定義したりできるかだ。それゆえにクラスベースのOOは静的(static)でプロトタイプベースのOOは動的(dynamic)だ。
どちらにも一長一短はある。オブジェクト指向の目的は、データに出来ることを限定することにある。その点においては、データに出来ることがクラスによって「固定」されているクラスベースの方が安心だ。プロトタイプベースだと、こんなことまで出来てしまう。
var a = new Date;
alert(a);
try{
delete Date;
var b = new Date;
alert(b);
}catch(e){
alert(e);
}
こういう自分の足を撃つような真似、というか「母殺し」は、正直あまり出来て欲しくないものではある。
しかし、限定=固定とは限らないし、変更できるからといって変更する必要もまたない。むしろ「プログラマーの意図どおりに動け」(do what I mean = DWIM)の観点から行けば、データに出来ることがプログラム実行時に変更できた方がありがたいことも多い。例えば
if (!Array.prototype.map) Array.prototype.map = function(f){
var result = [];
for(var i = 0, l = this.length; i < l; i++) result[i] = f(this[i]);
return result;
};
という具合に、「mapメソッドがない場合のみ、実行時に追加する」という柔軟性は静的な言語では望みがたい。また、生物というのはすべて「プロトタイプベース」なので、この方がむしろ「自然」にすら思える。
動的言語のクラスはプロトタイプ?
話がややこしくなるのは、実行時にクラスそのものを改変できる言語もかなり存在することだ。rubyもperlもpythonもそうである。こういった言語では、「クラス」と「プロトタイプ」の境界はかなりあやふやになる。
特にrubyのクラスというのは、クラスであると同時にオブジェクトでもある。そのことは以下を見れば一目瞭然だろう。
irb(main):001:0> 1.object_id => 3 irb(main):002:0> 2.object_id => 5 irb(main):003:0> 1.class => Fixnum irb(main):004:0> 1.class.object_id => 2007200 irb(main):005:0> 2.class.object_id => 2007200 irb(main):006:0> 1.class.object_id == 2.class.object_id => true
rubyのクラスを実行時に改変するのと、プロトタイプを動的に改変するのとの本質的な違いはそれほどないように見える。perlの場合、そもそもOOの仕組みそのものが型ベース(オブジェクトは型の名前を持ち、メソッドはその「型」=名前空間にある関数であり、その関数にはインスタンス(instance)は第一引数として渡される)であるが、任意の名前空間に任意の関数を動的に追加削除できる以上、やはり本質的に異なるとは言いがたい。
まとめ = 「Functionをプロトタイプとするオブジェクト」と呼ぼう
このように、動的言語においてはクラスもまたオブジェクトとして扱えてしまうのではあるが、それでも「プロトタイプベース」と呼ばれないのは、「クラスは改変可能ではあっても理由がなければ改変すべきではない」という不文律が普及しているという理由が一つと、実際のオブジェクトの生成が既存オブジェクトのコピーではなく、オブジェクト生成メソッドの実行(たいていnewと呼ばれる)によることが大きい。
で、最初の問題提起に戻る。AS3にはすでにclassが導入されているし、将来のJavaScriptにも導入されるようだが(改変不能のオブジェクトして実装?)、しかしFunctionがオブジェクトであり、そこから関数が生成されている以上、やはり「Functionクラスのオブジェクト」とは呼びたくない。呼ぶのであれば「Functionをプロトタイプとするオブジェクト」ないし「Functionから生成されるオブジェクト」ないし「Functionを母に持つオブジェクト」とするべきではないだろうか。
個人的には、最後の呼び方が気に入っている。が、かしこまって言うのであれば最初の例だろう。
Dan the Prototypal Being
See Also:

