こんにちは!新卒エンジニアの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の世界が待っていますので、ぜひ見てみてください!