本稿では、異なる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