よく耳にするTypeScriptの変性とは?

こんにちは!新卒エンジニアのhansprocsです!

TypeScriptでの型定義を行う時に、毎回変数に新しく型をつけるのはめんどくさいですよね。 そのためによく型同士を合わせたりもしています。すでにあるもので定義ができるのですごく便利。

しかし、通ると思っていた書き方からエラーが出ることがあります。 原理を知ることで、これからは悩みたくない!ということで今回は「変性」について調べました。

まずは変性を意識せずTypeScriptの挙動を見ていきたいと思います。

return値の型

以下は引数の型は同じで、return値の型が異なる時の代入を行なっています。

type Func = (a: string) => number | string;
declare const returnNumber: (a: string) => number;
declare const returnNumberString: (a: string) => number | string;
declare const returnNumberStringBoolean: (a: string) => number | string | boolean;

const func1: Func = returnNumber; // 🙆
const func2: Func = returnNumberString; // 🙆
const func3: Func = returnNumberStringBoolean; // 🙅

return値としてnumber | stringを持つ関数に、number | string | booleanというもっと広い型のものは代入できないですね。 逆に、もっと狭いnumberを代入することはできます。

このように、TypeScriptではreturnに自分よりもっと狭い型を代入することができます。

引数の型

次は、異なる引数の型を持って、同じreturnの型を持つ場合です。

type Func = (a: number | string) => number;
declare const argNumber: (a: number) => number;
declare const argNumberString: (a: number | string) => number;
declare const argNumberStringBoolean: (a: number | string | boolean) => number;

const func1: Func = argNumber; // 🙅
const func2: Func = argNumberString; // 🙆
const func3: Func = argNumberStringBoolean; // 🙆

引数の場合はreturnと逆の動作をしていますね。 TypeScriptでは引数に自分よりもっと広い型を代入することができます。

概念で理解してみる

return

return値は一つのであるため、代入を行うときにコンパイラーが判断できるものでないといけないです。

要するに、string | numberをstringに代入しようとするとコンパイラーはnumberかstringか判断できなくなる。 そのため広い型を狭い型に代入することができないですね。

引数

引数の場合広い型を狭い型に代入できるのは、関数自体は明確な値ではないからです。 したがって、もっと狭い型に代入すると、コンパイラーにもっと具体的に型推論を行わせることができます。

それで、変性とは?

ここまでの話は理解いただけましたか? あとは今まで見たきたものを言語化するだけですね。

定義

変性とは、言葉通りに「変わる性質」です。 TypeScript風で言うと、「ある型Tに対し持つ性質」になります。

種類

以下がその変性の種類です。 ここでスーパータイプは広い型を、サブタイプは狭い型を意味します。

名称 特徴
不変性(invariance) Tそのものが必要
共変性(covariance) Tそのものか、そのサブタイプが必要
反変性(contravariance) Tそのものか、そのスーパータイプが必要
双変性(bivariance) Tそのものか、そのスーパータイプ、もしくはサブタイプが必要

表の出典 TypeScript における変性(variance)について - 30歳からのプログラミング

ということはreturn値は共変性、引数は反変性を持つんですね!

終わりに

変性、理屈は理解できますが、概念で理解しようとするととても難しかったです。 コンパイラーの立場になって考えることが大事ですね。

今回は不変性、双変性は紹介だけになりましたが、 そこには奥深いTypeScriptの世界が待っていますので、ぜひ見てみてください!