型システムの理論の影

 プログラムにおける型システムってのは数学・論理学の型理論を基盤にしていて、抽象的なもやもやとした代物。この型システムの理論を現実に実装したものとして、多様なプログラム言語が存在するわけだ。

 鶏と卵という話だとどっちなんだろう。実装が先でそれを理論としてまとめたって感じなのか、理論がまず考えられてプログラム言語が実装されたのか…。最初のプログラム言語というと1954年に登場したFORTRANで、IBMのジョン・バッカス*1によって考案された。FORTRANにはいくつかの数値型がある。この頃に型システムの体系的な理論ができていたとは思えないから多分、実装が先行なんだろうな。

理論としての型、そのJava実装のクラス

 型システム(type system)でいう型(type)は型理論に基づくわけで、数学的な抽象概念に近い。例えば僕らが日常生活で数学的概念である1/3という値を厳密に使うことはないし、出来ない。でも1/3という数学的概念は実装を持って行使される。ぶっちゃけて言うと、僕らはケーキを3人に分配できる。

 そこに数学的な厳密さは当然ないわけだけど、実用上は困らない。僕らがプログラム言語という「実装」を通して触れる「型」ってのは、日常生活の1/3のようなもので、数学的な「型」ではなくて「型」の言語実装という影なんだ。*2

 Javaという言語の「型」は型システムを利用して実装された工学的な成果物で、数学的な「型」とは違ってしまっている。「Java::型」と「型システム::型」は違うんだよね。「Java::型」というと、まずプリミティブ型(PrimitiveType)と参照型(ReferenceType)という2分岐から始まるアレで、参照型はさらにクラス型(ClassType)とインターフェース型(InterfaceType)と配列型(ArrayType)に分かれる。*3

 クラス型の実装の記述をクラス宣言(ClassDeclaration)と言う。*4通常「クラス」と言った場合にこのクラス宣言によって規定されるクラス型の一実装を指して言うことが多いだろう。

 Javaにはリフレクションという言語機能があって、これらのクラスをプログラム中で抽象的に扱うためのクラス群が用意(java.lang.reflectパッケージ)されている。java.lang.Classというクラスはリフレクション機能のうち、クラス宣言を表現したクラスだ。実際の会話でこれは「Classクラス」(ひらがなで表記すると「くらすくらす」)と呼ばれる。あぁややこしい。

 結局、「クラス」という用語に言語を横断した確たる定義はなくて、「―言語におけるクラス」という、「型システム::型」の実装という名の影という捉え方をせざるをえないんだと思う。

 クラスとは何か - SiroKuro Pageの用語の混乱をいくらか解きほぐせただろうか。

その型は代役が務まるか

 型システム::型の話に戻って、ある型に他の型の代役が務まるかどうか、という話題を考えよう。

 厳密さを求めるなら、ある型を外から見た場合の姿、外部仕様と言うか、ようするに型のメンバは少なくとも同等以上に用意されていなければならない。HDMI端子しか付いていない24inchモニタは14inchアナログテレビの代役を務めることができないんだ。スーパーファミコンとか繋げれないんだよ。高機能なのと「型」として代役ができるのは違うってことだ。

 ここで、厳密に全ての端子を備えています!って謳うか、あるいは見切り発車で置き換えてみて、素知らぬ顔で代替えして動きつづけ、実際にコンポジット入力端子を接続しようとしたときに困ってエラーです、という2種類の実装が選べて、前者が静的、後者が動的な型付けの言語。

 動的言語は欠陥があってもそれに遭遇しなければいいじゃん、保険に入ってなくたって事が起こらなければ問題ないじゃん的な楽観性があって個人的には嫌い*5なんだけど、事が起こらない平穏な日常においてはそのぐらいゆるふわでいいんじゃないの、というのも分かる気はする。

 んで、代役ができます!と言った人に代役をやらせて、ちゃんと代役が務まるかどうかってのはやらせてみなければ分からなくて、静的言語の場合、「おまえそのメンバ持ってないじゃん」ということにはならないものの、ロジカルエラーが起こることは当然ありうる。

動的型のゆるふわ感

 動的型の型は型システムをゆるふわな感じに載せた感じ。オブジェクトが作られた時にはあった関数なのに、使おうと思ったらなくなってたとかいうことがある。ActionScript3あたりの型とか静的にわかるところは静的チェックしつつ、XMLリテラルとかの静的型付けじゃわかんないところはとりあえずコンパイルは通しておいて実行時に落とすというゆるふわ感。

 型の代入互換性のチェックを甘めにしておいて、確実に間違いだろ!ってとこだけ弾く、でも厳密さを求めるあまりに書類書くのが大変でさーみたいなことにならない程度に大雑把に通してしまえ、ってことができる静・動のグラデーションのただなかにあるような例だと思う。

 もっともJavaだって完全に厳密に型チェックできるわけでもなく、「強い型付け」というよりは「強めの型付け」と言った感じ。だから型付けなんてのはまったくなしから完璧までのグラデーションがあって、言語によってその位置が違う程度の話なんじゃないかと思うんだ。

 だから「型」という用語も「型システム::型」なら数学的な完璧を前提にしているだろうけども、特定言語の機能を持ってきて「これは型、これは型じゃない」なんて線引きを試みてもナンセンスなんじゃないだろうか。

型の互換性とプログラムとしての挙動のギャップ

 http://nazakki.blogspot.com/2009/03/blog-post_06.htmlで挙げられているような例を、設計としてうまく活用したのがGoFデザインパターンTemplateMethodパターン。これらのabstractメソッドがこう言う順にテンプレートから呼ばれますよーって約束事があって、それに合わせた実装をしなくちゃいけない。「型」としては互換性があったとしても、テンプレートのお約束に反した作りにしちゃうと変な動きをする。そりゃそうだよね。

 Javaの場合、すべてのクラスはjava.lang.Object型からの派生ということになるけども、このObject型ってabstractではないものの、いくつかのメソッドが定義されている。

 つまるところ、JavaというフレームワークにおけるTemplateMethodパターンなんだよね。Javaでプログラムする以上は、お約束ってやつを守らなくてはいけない。プログラムを書くと言うことは、自由なようでいて、プログラム言語の規定するルールという枠の中で約束を守って行動しなければいけないという程度の不自由がある。

 型システムが規定するのは物理的な代入互換性の規定。うまく動くように約束事で運用しようってのが契約に基づくプログラミング(programming by contract)。物理は殺人を禁止しないけど、社会は殺人を禁止するようなもの。「できる」と「やっていい」は違う。

フレームワークを設計するときに考えること

 自分は仕事ではフレームワーク的なものを作っているのだけど、やはり理想としては「型による制約」=「契約に基づくプログラミング」になっていることだと思う。

 ヒューマンエラーの対策として理想的なのは「間違えることができない」というのがあって、誤ってオーバーライドして変な動きしてあわわわわとか言う人がいないようにしたいところ。*6

 型システムは型の代入互換性を保証してくれるけど、現実のプログラムだと実装コードをどこかに載せないといけない。Javaでinterfaceによる華麗な継承階層を設計したとしても、implementsするときに実装コードへの委譲が泥くさいよねーということになることもある。*7

 型の制約を利用して、いまに便利な枠組みを作るかを考えていると、どうにも現行のJavaの型システム実装では弱いところがあったりして悩む。ジェネリクスとか使ってるとほころびが目立つんだよなぁ…。

まとめ

 そんなわけで

  • 型理論・型システムあたりの数学的な定義の「型」というのがある
  • 僕らがプログラム言語で触れる「型」ってのはその実装という影だ
  • ある型の代役として働かせる時に型の代入互換性に比較的厳密なのがJavaとかの型システム実装
  • プロトタイプ型OOPは代入互換性は厳密にやらないで動けばいいじゃん的実装
  • 型として代入互換性があるということと、プログラムが意図どおりに動くかは別の話題

*1:John Warner Backus (1924-2007) FORTRANの発明者と知られるほか、RFCなどのプロトコルの仕様書でお馴染みのバッカス・ナウア記法の発明者でもある。

*2:2009-03-06 - きしだのはてなでも似たようなことが言われているけど、中心の物体がこのエントリとは違う。

*3:Java言語仕様第3版 4.1 型と値の種類、4.3 参照型とその値

*4:Java言語仕様第3版 8.1 クラス宣言

*5:業務システムだとまさにそこが重要視されるわけで。

*6:Object#equals()の実装しくじってMapにデータを突っ込んだら消えるとか、そういうバグがおきたりするのですよ

*7:型の多重継承的なシチュエーション