Typescriptでの共変性 (covariance) と反変性 (contravariance)
Intro
共変性と反変性(covariance and contravariance)とは、データコンテナのサブタイプ関係が、そのデータ要素のサブタイプ関係に連動して定義されるという概念を指す
たとえば、T
が U
のサブタイプである場合、ジェネリック型 F<T>
が F<U>
のサブタイプであるかどうか、またはその逆の関係がどうなるかなど、F<T>
と F<U>
の関係を表すことです!
より具体的な例では、Cat
は Animal
のsubtypeで、 Cat
の配列と Animal
の配列どいう関係なのか?または、Cat
返す関数と Animal
返す関数がどいう関係なのかなど。
共変性と反変性を簡単な言葉で説明しながらTypescriptの具体例をあげて紹介しようと思います。
Definition
基本的に4つの種類があります。共変(covariant), 反変(contravariant), 非変(invariant) と 双変(bivariant) です。
Covariant
T
が U
のサブタイプであれば、F<T>
は F<U>
のサブタイプです。
Typescriptでは、関数またはメソッドの型は、その戻り値の型と共変します。
type Co<V> = () => V; // 関数の戻り値
function covariance<U, T extends U>(t: T, u: U, coT: Co<T>, coU: Co<U>) {
u = t; // ok
t = u; // error
coU = coT; // ok
coT = coU; // error
}
Typescriptにおける他の例は以下の通りです:
- オブジェクトとオブジェクトのプロパティ
- クラスのコンストラクタとインスタンス
Contravariant
T
が U
のサブタイプであれば、F<U>
は F<T>
のサブタイプです。
例えば、関数と関数のパラメータが反変です。
type Contra<V> = (v: V) => void;
function contravariance<U, T extends U>(t: T, u: U, contraT: Contra<T>, contraU: Contra<U>) {
u = t; // ok
t = u; // error
contraU = contraT; // error
contraT = contraU; // ok
}
他の例:
- オブジェクトとオブジェクトのキー
- クラスのコンストラクタとコンストラクタのパラメータ
Invariant
T
が U
のサブタイプであっても、F<T>
と F<U>
は関係ありません(F<T>
が F<U>
のサブタイプでなく、F<U>
も F<T>
のサブタイプでない)。
例えば、同じ型を返すパラメータと関数型です。
type In<V> = (v: V) => V;
function invariance<U, T extends U>(t: T, u: U, inT: In<T>, inU: In<U>) {
u = t; // ok
t = u; // error
inU = inT; // error
inT = inU; // error
}
Bivarant
T
が U
のサブタイプであれば、F<T>
は F<U>
のサブタイプであり、F<U>
も F<T>
のサブタイプであり、両方が正しいです。
Typescriptでは、関数とそのパラメータも双変です。これが、Typescriptがあまり強力な型システムを持たないと言われる有名な例です。
type Bi<V> = { foo(v: V): void };
function bivariance<U, T extends U>(t: T, u: U, biT: Bi<T>, biU: Bi<U>) {
u = t; // ok
t = u; // error
biU = biT; // ok
biT = biU; // ok
}
関数とパラメータが双変bivariantになると、何が問題かを見てみましょう。
function trainDog(d: Dog) { ... }
function cloneAnimal(source: Animal, done: (result: Animal) => void): void { ... }
let c = new Cat();
cloneAnimal(c, trainDog); // ok!!!
cloneAnimal
に trainDog
を渡すべきではないのに、問題なく渡せてしまいます。結果として、猫をクローン化し、それを犬としてトレーニングすることができてしまいます!🐶🙀
ただし、strictFunctionTypes というtypescriptのoptionを使うと、反変covariantとしてチェックされます!😻
結論
プログラミングにおける共変性と反変性の理解は、通常、サブタイプやスーパータイプが関与する場合に、データ型がどのように振る舞うかを理解することを指します。これらのfeaturesを理解することは、さまざまなプログラミングシナリオでより柔軟で堅牢なコードを作成することにつながります。特に、継承や多様性が関わるコレクションやAPIを扱う場合に重要です。
参考
- Covariance and contravariance (computer science) - Wikipedia
- Covariant, Contravariant, and Invariant in Typescript | Sandro Maglione
- Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript - Stack Overflow
- TypeScript: Documentation - TypeScript 2.6
- TypeScript: TSConfig Reference - Docs on every TSConfig option
Discussion