変性 (variance)
TypeScriptでは、型の互換性を判定する際に変性(variance)という概念が使われます。変性とは型同士の関係性を示すもので、TypeScriptにおいてはこの変性を示すためにはジェネリクスの型変数の前にinあるいはoutを付与します。
なお、ここで語られる変性はtsconfig.jsonのstrictFunctionTypesの設定でも変更することができます。
今回は特筆しない限りstrictFunctionTypesはfalseとして説明します。
📄️ strictFunctionTypes
引数型の変性のチェックを厳しくする
共変性 (Covariance)
共変性はジェネリクスの型変数にoutを付与した場合の変位です。共変性とはサブタイプの関係が保たれることを意味します。
反変性 (Contravariance)
反変性はジェネリクスの型変数にinを付与した場合の変位です。反変性とはサブタイプの関係が逆転することを意味します。
不変性 (Invariance)
不変性はジェネリクスの型変数にinとoutを付与した場合の変位です。不変性とは型が共変性でも反変性でもないことを意味します。
双変性 (Bivariance)
双変性はジェネリクスの型変数にinとoutを付与しない場合の変位です。
ここで例としてひとつの引数Iを受け取り戻り値Oを返す関数の型としてBivariantFunction<I, O>(変位をつけていないのでTypeScriptとしては双変と同じ)を定義します。
tsBivariantFunction <I ,O > = (arg :I ) =>O ;
tsBivariantFunction <I ,O > = (arg :I ) =>O ;
ここで引数Iを共変にしたCovariantFunction<in I, O>と戻り値Oを反変にしたContravariantFunction<I, out O>、引数も戻り値も不変にしたInvariantFunction<in out I, in out O>を定義します。するとそれらは次のように定義されます。
tsBivariantFunction <I ,O > = (arg :I ) =>O ;typeCovariantFunction <I , outO > =BivariantFunction <I ,O >;typeContravariantFunction <inI ,O > =BivariantFunction <I ,O >;typeInvariantFunction <in outI , in outO > =BivariantFunction <I ,O >;
tsBivariantFunction <I ,O > = (arg :I ) =>O ;typeCovariantFunction <I , outO > =BivariantFunction <I ,O >;typeContravariantFunction <inI ,O > =BivariantFunction <I ,O >;typeInvariantFunction <in outI , in outO > =BivariantFunction <I ,O >;
クラスの継承関係を使った例
継承関係がわかりやすくなるようにクラスA, B, Cを定義します。AはBを継承し、BはCを継承しており、メソッドを追加しました。
tsA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}
tsA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}
各変性の関数のIとOの両方をBにした関数を定義します。
tsbiFunc :BivariantFunction <B ,B >;declare constcoFunc :CovariantFunction <B ,B >;declare constcontraFunc :ContravariantFunction <B ,B >;declare constinFunc :InvariantFunction <B ,B >;
tsbiFunc :BivariantFunction <B ,B >;declare constcoFunc :CovariantFunction <B ,B >;declare constcontraFunc :ContravariantFunction <B ,B >;declare constinFunc :InvariantFunction <B ,B >;
これらの関数のジェネリクスを変更してみます。
tsf01 :BivariantFunction <A ,B > =biFunc ;constf02 :BivariantFunction <C ,B > =biFunc ;constf03 :BivariantFunction <B ,A > =biFunc ;constType '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'.: f04 BivariantFunction <B ,C > =biFunc ;constf05 :CovariantFunction <B ,A > =coFunc ;constType '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'.: f06 CovariantFunction <B ,C > =coFunc ;constType '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'.: f07 ContravariantFunction <A ,B > =contraFunc ;constf08 :ContravariantFunction <C ,B > =contraFunc ;constType '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'.: f09 InvariantFunction <A ,B > =inFunc ;constType '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'.: f10 InvariantFunction <C ,B > =inFunc ;constType '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'.: f11 InvariantFunction <B ,A > =inFunc ;constType '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'.: f12 InvariantFunction <B ,C > =inFunc ;
tsf01 :BivariantFunction <A ,B > =biFunc ;constf02 :BivariantFunction <C ,B > =biFunc ;constf03 :BivariantFunction <B ,A > =biFunc ;constType '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'.: f04 BivariantFunction <B ,C > =biFunc ;constf05 :CovariantFunction <B ,A > =coFunc ;constType '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'.: f06 CovariantFunction <B ,C > =coFunc ;constType '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'.: f07 ContravariantFunction <A ,B > =contraFunc ;constf08 :ContravariantFunction <C ,B > =contraFunc ;constType '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'.: f09 InvariantFunction <A ,B > =inFunc ;constType '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'.: f10 InvariantFunction <C ,B > =inFunc ;constType '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'.: f11 InvariantFunction <B ,A > =inFunc ;constType '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'.: f12 InvariantFunction <B ,C > =inFunc ;
これらの中でエラーになるものをまとめると
- f04は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、戻り値の- Cに対し- Bはメソッド- c()を持っていないためエラーになります
- f06は戻り値が共変でスーパータイプの割り当てが許容されますが、戻り値- Cは- Bのサブタイプなのでエラーになります
- f07は引数が反変でサブタイプの割り当てが許容されますが、引数- Aは- Bのスーパータイプなのでエラーになります
- f09は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f10は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f11は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f12は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
また、strictFunctionTypesをtrueにすると上記のエラーに加えて
- f01は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、引数の- Aはメソッド- b()を持っていないためエラーになります
ユニオン型を使った例
ユニオン型を使って継承関係を表してみます。
tsA = null;typeB = null | undefined;typeC = null | undefined | string;
tsA = null;typeB = null | undefined;typeC = null | undefined | string;
このときAはBの部分型であり、BはCの部分型です。言い換えるとA extends B、B extends Cです。
tsf01 :BivariantFunction <A ,B > =biFunc ;constf02 :BivariantFunction <C ,B > =biFunc ;constType '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'.: f03 BivariantFunction <B ,A > =biFunc ;constf04 :BivariantFunction <B ,C > =biFunc ;constType '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'.: f05 CovariantFunction <B ,A > =coFunc ;constf06 :CovariantFunction <B ,C > =coFunc ;constf07 :ContravariantFunction <A ,B > =contraFunc ;constType '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'.: f08 ContravariantFunction <C ,B > =contraFunc ;constType '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'.: f09 InvariantFunction <A ,B > =inFunc ;constType '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'.: f10 InvariantFunction <C ,B > =inFunc ;constType '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'.: f11 InvariantFunction <B ,A > =inFunc ;constType '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'.: f12 InvariantFunction <B ,C > =inFunc ;
tsf01 :BivariantFunction <A ,B > =biFunc ;constf02 :BivariantFunction <C ,B > =biFunc ;constType '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'.: f03 BivariantFunction <B ,A > =biFunc ;constf04 :BivariantFunction <B ,C > =biFunc ;constType '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'.: f05 CovariantFunction <B ,A > =coFunc ;constf06 :CovariantFunction <B ,C > =coFunc ;constf07 :ContravariantFunction <A ,B > =contraFunc ;constType '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'.: f08 ContravariantFunction <C ,B > =contraFunc ;constType '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'.: f09 InvariantFunction <A ,B > =inFunc ;constType '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'.: f10 InvariantFunction <C ,B > =inFunc ;constType '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'.: f11 InvariantFunction <B ,A > =inFunc ;constType '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'.: f12 InvariantFunction <B ,C > =inFunc ;
- f03は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、戻り値の- Aに対し- Bの- undefinedは- nullに割り当てることができないためエラーになります
- f05は戻り値が共変でスーパータイプの割り当てが許容されますが、戻り値- Aに対し- Bの- undefinedは- nullに割り当てることができないためエラーになります
- f08は引数が反変でサブタイプの割り当てが許容されますが、引数- Cの- stringは- Bに割り当てることができないためエラーになります
- f09は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f10は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f11は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
- f12は引数、戻り値ともに不変でスーパータイプとサブタイプの関係が許容されないためエラーになります
strictFunctionTypesをtrueにすると上記のエラーに加えて
- f02は引数、戻り値ともに双変でスーパータイプとサブタイプの関係が許容されますが、引数- Cの- stringは- Bに割り当てることができないためエラーになります
継承関係を見る
継承関係を見るためにConditional Typesを使ってみましょう。ある型TがUのサブタイプかどうかを判定する型IsSubType<T, U>を定義します。
tsIsSubType <T ,U > =T extendsU ? true : false;
tsIsSubType <T ,U > =T extendsU ? true : false;
先ほどのクラスA, B, CにIsSubType<T, U>を適用してみます。
tsA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}declare constt1 :IsSubType <A ,A >;declare constt2 :IsSubType <B ,B >;declare constt3 :IsSubType <C ,C >;declare constt4 :IsSubType <A ,B >;declare constt5 :IsSubType <B ,C >;declare constt6 :IsSubType <A ,C >;declare constt7 :IsSubType <B ,A >;declare constt8 :IsSubType <C ,B >;declare constt9 :IsSubType <C ,A >;
tsA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}declare constt1 :IsSubType <A ,A >;declare constt2 :IsSubType <B ,B >;declare constt3 :IsSubType <C ,C >;declare constt4 :IsSubType <A ,B >;declare constt5 :IsSubType <B ,C >;declare constt6 :IsSubType <A ,C >;declare constt7 :IsSubType <B ,A >;declare constt8 :IsSubType <C ,B >;declare constt9 :IsSubType <C ,A >;
自分自身のクラスあるいはサブクラスに限りtrueが返されることがわかります。
ユニオン型も見てみましょう。なお、こちらはユニオン型であるためDistributive Conditional Typeを使用します。
📄️ Distributive Conditional Types
Distributive Conditional Types は日本語では分配された条件型、条件付き型の分配、ユニオン分配、ユニオン型の分配法則などと呼ばれ、 Conditional Types が ジェネリクス型 に適用され、かつ型引数に ユニオン型 が与えられた場合に、そのユニオン型を構成する各メンバーに対して個別に条件判定が適用される(=分配される)性質を指します。
tsIsSubType <T ,U > = [T ] extends [U ] ? true : false;
tsIsSubType <T ,U > = [T ] extends [U ] ? true : false;
tsA = null;typeB = null | undefined;typeC = null | undefined | string;declare constt1 :IsSubType <A ,A >;declare constt2 :IsSubType <B ,B >;declare constt3 :IsSubType <C ,C >;declare constt4 :IsSubType <A ,B >;declare constt5 :IsSubType <B ,C >;declare constt6 :IsSubType <A ,C >;declare constt7 :IsSubType <B ,A >;declare constt8 :IsSubType <C ,B >;declare constt9 :IsSubType <C ,A >;
tsA = null;typeB = null | undefined;typeC = null | undefined | string;declare constt1 :IsSubType <A ,A >;declare constt2 :IsSubType <B ,B >;declare constt3 :IsSubType <C ,C >;declare constt4 :IsSubType <A ,B >;declare constt5 :IsSubType <B ,C >;declare constt6 :IsSubType <A ,C >;declare constt7 :IsSubType <B ,A >;declare constt8 :IsSubType <C ,B >;declare constt9 :IsSubType <C ,A >;
AはBの部分型であり、BはCの部分型であるためt4、t5、t6がtrueになることがわかります。