😻

Typescriptでの共変性 (covariance) と反変性 (contravariance)

2024/03/26に公開

Intro

共変性反変性(covariance and contravariance)とは、データコンテナのサブタイプ関係が、そのデータ要素のサブタイプ関係に連動して定義されるという概念を指す

共変性と反変性 (計算機科学) - Wikipedia

たとえば、TU のサブタイプである場合、ジェネリック型 F<T>F<U> のサブタイプであるかどうか、またはその逆の関係がどうなるかなど、F<T>F<U> の関係を表すことです!

より具体的な例では、CatAnimal のsubtypeで、 Cat の配列と Animal の配列どいう関係なのか?または、Cat 返す関数と Animal 返す関数がどいう関係なのかなど。

共変性と反変性を簡単な言葉で説明しながらTypescriptの具体例をあげて紹介しようと思います。

Definition

基本的に4つの種類があります。共変(covariant), 反変(contravariant), 非変(invariant)双変(bivariant) です。

Covariant

TU のサブタイプであれば、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

TU のサブタイプであれば、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

TU のサブタイプであっても、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

TU のサブタイプであれば、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!!!

cloneAnimaltrainDog を渡すべきではないのに、問題なく渡せてしまいます。結果として、猫をクローン化し、それを犬としてトレーニングすることができてしまいます!🐶🙀

ただし、strictFunctionTypes というtypescriptのoptionを使うと、反変covariantとしてチェックされます!😻

結論

プログラミングにおける共変性と反変性の理解は、通常、サブタイプやスーパータイプが関与する場合に、データ型がどのように振る舞うかを理解することを指します。これらのfeaturesを理解することは、さまざまなプログラミングシナリオでより柔軟で堅牢なコードを作成することにつながります。特に、継承や多様性が関わるコレクションやAPIを扱う場合に重要です。

参考

SocialPLUS Tech Blog

Discussion