変性 (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としては双変と同じ)を定義します。
tstypeBivariantFunction <I ,O > = (arg :I ) =>O ;
tstypeBivariantFunction <I ,O > = (arg :I ) =>O ;
ここで引数Iを共変にしたCovariantFunction<in I, O>と戻り値Oを反変にしたContravariantFunction<I, out O>、引数も戻り値も不変にしたInvariantFunction<in out I, in out O>を定義します。するとそれらは次のように定義されます。
tstypeBivariantFunction <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 >;
tstypeBivariantFunction <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を継承しており、メソッドを追加しました。
tsclassA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}
tsclassA {publica (): void {console .log ("a");}}classB extendsA {publicb (): void {console .log ("b");}}classC extendsB {publicc (): void {console .log ("c");}}
各変性の関数のIとOの両方をBにした関数を定義します。
tsdeclare constbiFunc :BivariantFunction <B ,B >;declare constcoFunc :CovariantFunction <B ,B >;declare constcontraFunc :ContravariantFunction <B ,B >;declare constinFunc :InvariantFunction <B ,B >;
tsdeclare constbiFunc :BivariantFunction <B ,B >;declare constcoFunc :CovariantFunction <B ,B >;declare constcontraFunc :ContravariantFunction <B ,B >;declare constinFunc :InvariantFunction <B ,B >;
これらの関数のジェネリクスを変更してみます。
tsconstf01 :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 ;
tsconstf01 :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()を持っていないためエラーになります
ユニオン型を使った例
ユニオン型を使って継承関係を表してみます。
tstypeA = null;typeB = null | undefined;typeC = null | undefined | string;
tstypeA = null;typeB = null | undefined;typeC = null | undefined | string;
このときAはBの部分型であり、BはCの部分型です。言い換えるとA extends B、B extends Cです。
tsconstf01 :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 ;
tsconstf01 :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>を定義します。
tstypeIsSubType <T ,U > =T extendsU ? true : false;
tstypeIsSubType <T ,U > =T extendsU ? true : false;
先ほどのクラスA, B, CにIsSubType<T, U>を適用してみます。
tsclassA {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 >;
tsclassA {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を使用します。
📄️ ユニオン分配
ユニオン分配はジェネリクスで使われる型変数Tに対しユニオン型が指定された場合、その各要素に対してジェネリクスの型変数を適用することを指します。
tstypeIsSubType <T ,U > = [T ] extends [U ] ? true : false;
tstypeIsSubType <T ,U > = [T ] extends [U ] ? true : false;
tstypeA = 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 >;
tstypeA = 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になることがわかります。