カテゴリー(分類タグ)に「檜山用メモ」って入っているときは、独り言の雰囲気があるので、「です・ます」じゃなくて「だ・である」になるのだ。「です・ます」で独り言はしないな、なぜか。
「プログラマのためのJavaScript」と「micro*」という2つの続き物を書いているが、そのネタがJavaScriptなので、JavaScriptに関して調べた。で、それに関連して感じたことを、とりとめなく書き並べてみる。
[追記]: 最初、コンパイル時に決まるという意味合いで「静的」、実行時にしかわからない/実行時にするという意味合いで「動的」を使っていた。が、読み直すと漢字だと区別しにくいので、「スタティック」「ダイナミック」に一括置換。そしたら、「スタティッククラス」なんて単語が出てきて、Javaのstaticにネストしたクラスとか、スタティックメソッドばかりからなるクラスとか誤解されまいか、と心配に…。まー、きりがないので、この程度の用語法でいいとしましょう。[追記終わり]
●型なしオブジェクト、ダイナミックオブジェクト
オブジェクトxが typeof x == 'object'
のとき「object型である」ということにする。JavaScriptのobject型オブジェクトは、それ以上に細かく型付け(typing)することはできない。強いて言えば、コンストラクタがサブタイプを定義しているように思えるが、思えるだけで、コンストラクタが型システム内に正当な位置を持つわけではない。
これは気持ち悪いことだが、進化的オブジェクト(evolving objects)をサポートするには好都合である。進化的オブジェクトとは、生成後に特徴や能力が変わるオブジェクトだ。フィールドやメソッドの追加/削除がダイナミックにできなくてはならない。当然に、スタティックに型付けするのは困難だ。つまり、進化的オブジェクトは型なし(正確には、スタティックで決定性の型は持たない)でダイナミックな存在物となる。
一方、クラスとは、スタティックで決定性の型を定義する方法である。クラスはオブジェクトの構築計画書(スタティックな仕様記述)であり、実行時にはその計画に従ったオブジェクトの生成機能の役割を果たす。よって、クラスはスタティックな型システム内にキチンと配置される存在だ。
●ダイナミックな型システム
僕は進化的オブジェクトの必要性を強く感じるので、スタティックで決定性の型は好きじゃない。だから、クラスや継承も好きじゃない。だが、「モノの分類体系」として、ある程度は精密な型システムを構成できないのはヤッパリ不便である。
実際には、ダイナミックな型システムであって、スタティックな型システムをも含むものを構成できる。JavaScript 2.0風の記法で説明しよう。まず、次のクラス定義を考える。
class Person {
var age:Integer;
var givenName:String;
var familyName:String;
}
これは従来のクラスであり、スタティックに決まる型とみなせる。age, givenName, familyNameはfixed(あるいはdefinite)プロパティと呼ばれ、生成時から存在し削除できない。この非ダイナミックなクラスは、age, givenName, familyName以外のプロパティを許さない。
しかし、以下のようにdynamic修飾子(JavaScrip 2.0では修飾子ではなくてattributeと呼ぶ)を付けると、dynamicプロパティを許すことになる。dynamicプロパティとは、クラス定義には現れず、生成後に追加/削除可能なプロパティだ。
dynamic class Person {
var age:Integer;
var givenName:String;
var familyName:String;
}
下は、従来のObjectと事実上同じクラスの定義である。
dynamic class PlainObject {}
dynamic修飾子を言語仕様に入れるだけでも、一部分だけ制約を持つようなダイナミックオブジェクトを定義できる。このとき、ダイナミックであっても、特定クラスに所属するオブジェクト(つまり、インスタンス)とみなせるので、型システムの詳細な分類の網で捉えることができる。
●ダイナミックオブジェクトのダイナミック型変換
Personを継承(普通の意味の継承)してPerson2を定義する。
dynamic class Person2 extends Person {
var sex:Sex; // Sexはトークン列挙型のようなものとする
}
スタティックに決定可能な型階層のなかで、Person2はPersonのサブタイプになるから、Person2→Personの自動型変換(型強制;coercion)は何の問題もない。が、次の例はどうだろう。
var p = new Person();
p.age = 23;
p.givenName = "トン吉";
p.familyName = "板東";
p.sex = 'male';
このpはPerson2型のインスタンスとみなせるだろうか? コンストラクタPerson2()を使って生成されたインスタンスは定義により(by definition)sexプロパティを持つが、このpはたまたま(by accident )sexプロパティを持つことになった。このpをPerson2型を期待している関数の引数に渡していいだろうか。
var roomNo = assignRoomInTheHotel(p); //ホテルに女性階あり
コンパイラが、assignRoomInTheHotel(p) という関数呼び出しが正当(むしろ問題なし)と判断するのは困難である。実行時の引数チェックでも、arg1.getClass() == Person2 のような判断ロジックでは実引数pは拒否されてしまう。
とはいえ、定義にしたがって毎回プロパティごとの型チェック(再帰的かも)をするのは過剰なオーバヘッドで許されないだろう。となると、“偶発的に正しいオブジェクト”を、あるクラスのインスタンスとみなすことは困難である。
●養子縁組方式の型強制
それでも、“偶発的に正しいオブジェクト”を、あたかも“最初から正しいオブジェクト”のようにみなしたい要求はある。
インスタンスの所属クラスとは、出生証明あるいは履歴書みたいなもので、特定インスタンスがある性質を持つことを保証する目印だ。そして型システムとは、「しかしじかの性質を持つことを保証する」ための仕掛けである。「所属クラスを見て判断」と「(実行時に)オブジェクト自体のプロパティを再帰的にたどって調べる判断」は、証明書だけで(コンパイル時または実行時に)書類審査するか、書類なしで本人を徹底的に調べて判断するかの違いである。
僕のクラス嫌いは、「単一ラベルに基づく分類/審査」が性に合わないせいかもしれない(まったく理論的な根拠にはならない! が)。
さて、Person2型の引数を期待しているassignRoomInTheHotel関数に、Person型として生成されたオブジェクトpを渡すには、どこかで型強制をしなくてはならない。つまりは、明示的な(そして実行時の)キャストが必要になる。キャストをJavaScrip風に関数記法で書けば、Peron2(p)となる。
(コンストラクではない)関数Person2は、引数がPerson2としてふさわしいかを検査して、所属クラスをPerson2に書き換える。つまり、証明書の書き換え再発行をする。ここで、せっかく証明してやったのに後でまた必須プロパティを書き換えられると困る(型システムが破綻する)から、なんらかの制約(あるいは誓約)が必要になる(一回こっきりの証明書もあり得るかもしれないが)。
比喩的に言えば、Personから生成されたpを、「Person2から生まれた」と出生を書き換えてしまうわけだから、これは養子縁組みである(実際の養子では、出生記録は残るのだろうが)。今の例では、養子縁組の過程(明示的実行時型変換)がダウンキャストになっていたが、まったく無関係なクラス間でも養子縁組はありえる。
●進化的なオブジェクトの姿
進化的なオブジェクトといえども、生まれたときは特定の所属クラスを持っているだろう。もちろん、そのクラスはダイナミッククラス(dynamic class)でなくてはならない。
所属クラスがダイナミックなら、インスタンスは(ある範囲内で)ダイナミックに性質や能力を変えていくことができる。ある段階で、所属クラスを書き換えることも許せば、必要に応じて証明書(型システムにおけるパスポート)を取り替えられる。
養子縁組の例えを使ったが、型を資格のようなものだと考えてもいい。つまり、ある条件を満たせば資格を取得できて、資格を必要な行為や職務が許される、と。
この資格のメタファーを使うと、「一人でいくつも資格をもってもいいのじゃないか」という話がでる。所属クラスが複数? ちょっと面倒だね。おそらく、血縁や出生に関する戸籍データと、仕事や社会における役割/適性を示す資格は別物であるように、メソッド共有を含む内在的メカニズム(クラス)と、仕様としての型(インターフェース、より正確にはシグニチャ)を区別すべきなんだろうね。
いずれにしても、進化的オブジェクトは必要なんだ。データ表現の観点からは、ゆるく構造化されたデータをスタティックに強く型付けされたオブジェクトで扱うのはとても大変だからね。