本稿では、異なる2つの型システム「公称型」と「構造的部分型」の差異について説明します。
型の互換性とは?
まず型の互換性とは何でしょうか? TypeScriptでは、ある型に別の型を代入できるかどうかが「互換性」の判断基準になります。例えば、1はnumberに代入できるので、1はnumberに互換しています。一方、trueはnumberに代入できないので、trueはnumberとは互換していないことになります。
異なる2つの型システム
TypeScriptは、互換性の有無の判断基準(つまり、代入できるかどうかの基準)が、JavaやPHPなどとは異なっています。どのように異なるのか、それぞれ見ていきましょう。
公称型 - 継承関係に着目するJava
JavaやPHPは、公称型(nominal typing)という型システムを採用しています。公称型では、型の互換性は、オブジェクト同士の継承関係(is-a関係)に着目して判断します。
例えば、AnimalとUserの2つのクラスが、全く同じnameプロパティを持っていて、同じように取り扱えたとしても、お互いに継承関係が無い場合は、User型の変数にAnimalインスタンスを代入することも、その逆もできません。互換性がないわけです。逆に、CatクラスがAnimalを継承している場合、Animal型の変数にCatは代入可能です。CatはAnimalとの互換性があることになります。
AnimalとUserが同じプロパティ、同じメソッドを持っていれば、オブジェクトの「能力」は同じです。なので、Userを使うところにAnimalを使えても良さそうです。それが仕様上の間違いだとしても、論理エラーにはならないためです。
しかし、こうした交換を許さないのが公称型なのです。あくまで、継承関係があるかが大事。「能力」よりも「血統」重視の型システムと言えそうです。
構造的部分型 - 構造に着目するTypeScript
TypeScriptは、構造的部分型(structural subtyping)という型システムを採用しています。構造的部分型は、継承関係ではなく、オブジェクトが持っているプロパティが互換しているかどうか、つまり構造が同じかどうかに着目します。
例えば、AnimalとUserの2つのクラスに、継承関係がなかったとしても、同じnameプロパティを持っていれば、User型の変数にAnimalを代入できます。その逆も可能です。つまり、お互いに互換性があるわけです。
class Animal {
public name: string = ''
}
class User {
public name: string = ''
}
let user: User = new User()
let animal: Animal = new Animal()
user = animal // OK
animal = user // OK
AnimalとUserが同じインターフェイスならば、オブジェクトの「能力」は同じです。継承関係がなくとも、「能力」が同じかどうかが大事。「血統」よりも「能力」重視なのが構造的部分型と言えそうです。
・・・
本稿では、インターフェイスが同じならTypeScriptでは互換性があると説明しました。しかし、これは正確ではありません。同じインターフェイスを持ったクラス同士でも、互換性がない、つまり、代入できない場合があります。それについては次の投稿で説明していきたいと思います。
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします
→Twitter@suin