いきさつ
おさらい
int n;などと宣言することで「これは整数だよ」という情報を変数につけるのが静的型付けP.129 「動的型付け > メリットとデメリット」ではこう書いた。
このような方法で値の種類を扱うことで、従来の静的型付け言語ではできなかったような柔軟な処理ができるようになりました。実行時まで型を決めないことや、実行時に型が変わることが可能になったのです。しかし、これはデメリットでもありました。静的型付け言語は、コンパイル時に型が決まり、コンパイル時に型の整合性がチェックされました。この型チェックのおかげで、一部のバグには実行する前に気付くことができました。しかし動的型付け言語ではそれができなくなってしまいました。
このデメリットを解決したいというニーズが高まった。特にJavaScriptでそのニーズが強かった。
このデメリットを解消するアプローチは2つある。
Aのアプローチ
Bのアプローチ
「静的型付けの言語でプログラムを記述し、それをJavaScriptに変換する」の「静的型付け」の部分は、言語によって異なる。Dartは2011年にリリースされたが、2018年に型の仕組みを変えて再リリースされた。また「静的型付け」という言葉で「Javaみたいな型付けの仕組み」と連想されやすいが、これは必ずしも正しくない。
2014年にこの本を書いた時には「動的型付け v.s. 静的型付け」のシンプルな構図で説明できた。2019年現在、かつて「静的型付け」の一言で済まされていた概念が、より細かく分かれていく過渡期にある。Pythonのように、動的型付け言語が事後的に静的型検査を手に入れる例もある。そして「静的型付け」の中に、新たな対立構図「名前的(nominal) v.s. 構造的(structural)」が生まれている。
型Aが求められている文脈で型Bを使うことができるのはどのような場合か?
例えば型Aの変数xに型Bのオブジェクトを代入できるのはどういう場合か? :
A x;
B y = new B();
x = y; // OK? NG?
double z = 1;がOK、それ以前はNGだった。こういう挙動の差はここでの本題ではない)名前的型システム(nominal type system)の言語では、型には名前がついている。たとえ構造がまったく同じでも、名前が違えば違う型である。下記のコードでは、T1とT2は構造がまったく同じなのに、代入するとエラーになる。
Dart
class T1 {
num x;
}
class T2 {
num x;
}
void main() {
T1 a;
T2 b = new T2();
a = b; // ERROR: A value of type 'T2' can't be assigned to a variable of type 'T1'.
}
この種の型システムでは、明示的に「型T2は型T1の部分型(subtype)だ」と宣言することで、T1が求められる文脈でT2を使うことができるようになる。下記のコードではextendsが部分型の宣言にあたる。 Dart
class T1 {
num x;
}
class T2 extends T1{ // 明示的に「T2はT1の部分型」と宣言した
num x;
}
void main() {
T1 a;
T2 b = new T2();
a = b; // OKになった
}
一方で、構造的型システム(structural type system)の言語では、型がどういう構造を持っているかで判断をする。型には名前がついているとは限らないし、たとえ名前が違っても、構造が同じならば互換性があるものとして扱われる。
TypeScript
type T1 = {
x : number
}
type T2 = {
x : number
}
let a:T1;
let b:T2 = {x: 1};
a = b; // OK
このコードのlet b:T2 = {x: 1}の、{x: 1}の部分は名前のないオブジェクト型である。
これはT1ともT2とも同じ形をしているので、エラーになることなく代入できる。
1985年に生まれたC++や1995年に生まれたJavaは名前的型システムを採用していた。この2つの言語はとてもメジャーになったので、extendsやimplementsで部分型関係の宣言をすることに慣れ親しんだ人も多いだろう。一方、型理論の研究においては構造的型システムの方が主流だった。その結果、JavaScriptを生成するための静的型付け言語を設計する際に、Javaなどを参考に名前的型システムを採用した言語と、型理論を参考に構造的型システムを採用する言語が混在することになってしまった。
(名前的型システムには、部分型かどうかの判定処理が軽いメリットがあった。しかしJava誕生から25年も経つ現在、コンピュータの性能も大きく進歩したため、名前的型システムを選ぶ理由にはならないのではないか。それよりも、Javaに慣れ親しんだ人がたくさんいる、という歴史的経緯が大きいのではないか。)
(Javaは2004年にジェネリクス(総称型)の機能を採用した。これは名前的型だったJavaが構造的型とのハイブリッドに進む方向なのだが、それをページを割いて説明するかどうかは要検討)