メインコンテンツまでスキップ

変性 (variance)

TypeScriptでは、型の互換性を判定する際に変性(variance)という概念が使われます。変性とは型同士の関係性を示すもので、TypeScriptにおいてはこの変性を示すためにはジェネリクスの型変数の前にinあるいはoutを付与します。

なお、ここで語られる変性はtsconfig.jsonのstrictFunctionTypesの設定でも変更することができます。
今回は特筆しない限りstrictFunctionTypesfalseとして説明します。

📄️ strictFunctionTypes

引数型の変性のチェックを厳しくする

共変性 (Covariance)

共変性はジェネリクスの型変数にoutを付与した場合の変位です。共変性とはサブタイプの関係が保たれることを意味します。

反変性 (Contravariance)

反変性はジェネリクスの型変数にinを付与した場合の変位です。反変性とはサブタイプの関係が逆転することを意味します。

不変性 (Invariance)

不変性はジェネリクスの型変数にinoutを付与した場合の変位です。不変性とは型が共変性でも反変性でもないことを意味します。

双変性 (Bivariance)

双変性はジェネリクスの型変数にinoutを付与しない場合の変位です。

ここで例としてひとつの引数Iを受け取り戻り値Oを返す関数の型としてBivariantFunction<I, O>(変位をつけていないのでTypeScriptとしては双変と同じ)を定義します。

ts
type BivariantFunction<I, O> = (arg: I) => O;
ts
type BivariantFunction<I, O> = (arg: I) => O;

ここで引数Iを共変にしたCovariantFunction<in I, O>と戻り値Oを反変にしたContravariantFunction<I, out O>、引数も戻り値も不変にしたInvariantFunction<in out I, in out O>を定義します。するとそれらは次のように定義されます。

ts
type BivariantFunction<I, O> = (arg: I) => O;
type CovariantFunction<I, out O> = BivariantFunction<I, O>;
type ContravariantFunction<in I, O> = BivariantFunction<I, O>;
type InvariantFunction<in out I, in out O> = BivariantFunction<I, O>;
ts
type BivariantFunction<I, O> = (arg: I) => O;
type CovariantFunction<I, out O> = BivariantFunction<I, O>;
type ContravariantFunction<in I, O> = BivariantFunction<I, O>;
type InvariantFunction<in out I, in out O> = BivariantFunction<I, O>;

クラスの継承関係を使った例

継承関係がわかりやすくなるようにクラスA, B, Cを定義します。ABを継承し、BCを継承しており、メソッドを追加しました。

ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}

各変性の関数のIOの両方をBにした関数を定義します。

ts
declare const biFunc: BivariantFunction<B, B>;
declare const coFunc: CovariantFunction<B, B>;
declare const contraFunc: ContravariantFunction<B, B>;
declare const inFunc: InvariantFunction<B, B>;
ts
declare const biFunc: BivariantFunction<B, B>;
declare const coFunc: CovariantFunction<B, B>;
declare const contraFunc: ContravariantFunction<B, B>;
declare const inFunc: InvariantFunction<B, B>;

これらの関数のジェネリクスを変更してみます。

ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
const f04: BivariantFunction<B, C> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f05: CovariantFunction<B, A> = coFunc;
const f06: CovariantFunction<B, C> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.
 
const f07: ContravariantFunction<A, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f08: ContravariantFunction<C, B> = contraFunc;
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Type 'A' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Type 'A' is not assignable to type 'B'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'B' is not assignable to type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'B' is not assignable to type 'C'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Type 'A' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Type 'A' is not assignable to type 'B'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.
ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
const f04: BivariantFunction<B, C> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, C>'. Property 'c' is missing in type 'B' but required in type 'C'.
 
const f05: CovariantFunction<B, A> = coFunc;
const f06: CovariantFunction<B, C> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.
 
const f07: ContravariantFunction<A, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<A, B>'. Property 'b' is missing in type 'A' but required in type 'B'.
const f08: ContravariantFunction<C, B> = contraFunc;
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Type 'A' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<A, B>'. Type 'A' is not assignable to type 'B'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'B' is not assignable to type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'B' is not assignable to type 'C'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Type 'A' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, A>'. Type 'A' is not assignable to type 'B'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'B' is not assignable to type 'C'.

これらの中でエラーになるものをまとめると

  1. f04は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、戻り値のCに対しBはメソッドc()を持っていないためエラーになります
  2. f06は戻り値が共変でスーパータイプの割り当てが許容されますが、戻り値CBのサブタイプなのでエラーになります
  3. f07は引数が反変でサブタイプの割り当てが許容されますが、引数ABのスーパータイプなのでエラーになります
  4. f09は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  5. f10は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  6. f11は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  7. f12は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります

また、strictFunctionTypestrueにすると上記のエラーに加えて

  1. f01は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、引数のAはメソッドb()を持っていないためエラーになります

ユニオン型を使った例

ユニオン型を使って継承関係を表してみます。

ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;

このときABの部分型であり、BCの部分型です。言い換えるとA extends BB extends Cです。

ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f04: BivariantFunction<B, C> = biFunc;
 
const f05: CovariantFunction<B, A> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f06: CovariantFunction<B, C> = coFunc;
 
const f07: ContravariantFunction<A, B> = contraFunc;
const f08: ContravariantFunction<C, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'.
ts
const f01: BivariantFunction<A, B> = biFunc;
const f02: BivariantFunction<C, B> = biFunc;
const f03: BivariantFunction<B, A> = biFunc;
Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'BivariantFunction<B, B>' is not assignable to type 'BivariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f04: BivariantFunction<B, C> = biFunc;
 
const f05: CovariantFunction<B, A> = coFunc;
Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'CovariantFunction<B, B>' is not assignable to type 'CovariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f06: CovariantFunction<B, C> = coFunc;
 
const f07: ContravariantFunction<A, B> = contraFunc;
const f08: ContravariantFunction<C, B> = contraFunc;
Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.2322Type 'ContravariantFunction<B, B>' is not assignable to type 'ContravariantFunction<C, B>'. Type 'C' is not assignable to type 'B'. Type 'string' is not assignable to type 'B'.
 
const f09: InvariantFunction<A, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<null, B>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f10: InvariantFunction<C, B> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<C, B>'. Type 'C' is not assignable to type 'B'.
const f11: InvariantFunction<B, A> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, null>'. Type 'B' is not assignable to type 'null'. Type 'undefined' is not assignable to type 'null'.
const f12: InvariantFunction<B, C> = inFunc;
Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'.2322Type 'InvariantFunction<B, B>' is not assignable to type 'InvariantFunction<B, C>'. Type 'C' is not assignable to type 'B'.
  1. f03は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、戻り値のAに対しBundefinednullに割り当てることができないためエラーになります
  2. f05は戻り値が共変でスーパータイプの割り当てが許容されますが、戻り値Aに対しBundefinednullに割り当てることができないためエラーになります
  3. f08は引数が反変でサブタイプの割り当てが許容されますが、引数CstringBに割り当てることができないためエラーになります
  4. f09は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  5. f10は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  6. f11は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
  7. f12は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります

strictFunctionTypestrueにすると上記のエラーに加えて

  1. f02は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、引数CstringBに割り当てることができないためエラーになります

継承関係を見る

継承関係を見るためにConditional Typesを使ってみましょう。ある型TUのサブタイプかどうかを判定する型IsSubType<T, U>を定義します。

ts
type IsSubType<T, U> = T extends U ? true : false;
ts
type IsSubType<T, U> = T extends U ? true : false;

先ほどのクラスA, B, CIsSubType<T, U>を適用してみます。

ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: false
declare const t5: IsSubType<B, C>;
const t5: false
declare const t6: IsSubType<A, C>;
const t6: false
 
declare const t7: IsSubType<B, A>;
const t7: true
declare const t8: IsSubType<C, B>;
const t8: true
declare const t9: IsSubType<C, A>;
const t9: true
ts
class A {
public a(): void {
console.log("a");
}
}
 
class B extends A {
public b(): void {
console.log("b");
}
}
 
class C extends B {
public c(): void {
console.log("c");
}
}
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: false
declare const t5: IsSubType<B, C>;
const t5: false
declare const t6: IsSubType<A, C>;
const t6: false
 
declare const t7: IsSubType<B, A>;
const t7: true
declare const t8: IsSubType<C, B>;
const t8: true
declare const t9: IsSubType<C, A>;
const t9: true

自分自身のクラスあるいはサブクラスに限りtrueが返されることがわかります。

ユニオン型も見てみましょう。なお、こちらはユニオン型であるためDistributive Conditional Typeを使用します。

📄️ ユニオン分配

ユニオン分配はジェネリクスで使われる型変数Tに対しユニオン型が指定された場合、その各要素に対してジェネリクスの型変数を適用することを指します。

ts
type IsSubType<T, U> = [T] extends [U] ? true : false;
ts
type IsSubType<T, U> = [T] extends [U] ? true : false;
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: true
declare const t5: IsSubType<B, C>;
const t5: true
declare const t6: IsSubType<A, C>;
const t6: true
 
declare const t7: IsSubType<B, A>;
const t7: false
declare const t8: IsSubType<C, B>;
const t8: false
declare const t9: IsSubType<C, A>;
const t9: false
ts
type A = null;
type B = null | undefined;
type C = null | undefined | string;
 
declare const t1: IsSubType<A, A>;
const t1: true
declare const t2: IsSubType<B, B>;
const t2: true
declare const t3: IsSubType<C, C>;
const t3: true
 
declare const t4: IsSubType<A, B>;
const t4: true
declare const t5: IsSubType<B, C>;
const t5: true
declare const t6: IsSubType<A, C>;
const t6: true
 
declare const t7: IsSubType<B, A>;
const t7: false
declare const t8: IsSubType<C, B>;
const t8: false
declare const t9: IsSubType<C, A>;
const t9: false

ABの部分型であり、BCの部分型であるためt4t5t6trueになることがわかります。