サブセットとサブタイプ
サブタイプとは?
number
、boolean
、string
のような型は、取りうる値の集合を表します。number
はすべての可能な数値を表すので、単一の数値(例えば42
)はnumber
型のサブタイプになります。逆に、number
は型42
のスーパータイプになります。
ある型が別の型のサブタイプであるかどうかを知りたい場合は、両方の型の取りうるすべての値を調べ、もう一方の値のサブセットを持っているかどうかを判断する必要があります。
例えば、1から3までの数値を表すTypeA
(ユニオン型とリテラル型の組み合わせ)と、1から5までの数値を表すTypeB
があるとします。TypeA
はTypeB
のサブセットであるため、TypeA
はTypeB
のサブタイプと見なされます。
1type TypeA = 1 | 2 | 3;2type TypeB = 1 | 2 | 3 | 4 | 5;
文字列 "A"、"B"、"C"を表すTypeLetters
と、数値1、2、3を表すTypeNumbers
を考えます。これらはそれぞれ完全に異なる値の集合を含むため、互いにサブタイプにはなりません。
1type TypeLetters = "A" | "B" | "C";2type TypeNumbers = 1 | 2 | 3;
最後に、1から3までの数値を表すTypeA
と、3から5までの数値を表すTypeB
があったとします。これらも互いにサブタイプにはなりません。両方とも3を持ち数値を表していますが、それぞれ固有の要素を持っています。
1type TypeA = 1 | 2 | 3;2type TypeB = 3 | 4 | 5;
サブタイプはいつ使用されるのか?
Flowが行うほとんどの作業は、型同士を比較することです。
例えば、関数を正しく呼び出しているかどうかを知るためには、Flowは渡している引数と関数が期待するパラメータを比較する必要があります。
これは多くの場合、渡している値が期待する値のサブタイプであるかどうかを判断することを意味します。
したがって、1から5までの数値を期待する関数を作成した場合、その集合の任意のサブタイプが受け入れられます。
1function f(param: 1 | 2 | 3 | 4 | 5) {2 // ...3}4
5declare const oneOrTwo: 1 | 2; // Subset of the input parameters type.6declare const fiveOrSix: 5 | 6; // Not a subset of the input parameters type.7
8f(oneOrTwo); // Works!9f(fiveOrSix); // Error!
9:3-9:11: Cannot call `f` with `fiveOrSix` bound to `param` because number literal `6` [1] is incompatible with literal union [2]. [incompatible-call]
複合型のサブタイプ
Flowはプリミティブ値の集合だけでなく、オブジェクト、関数、および言語に現れるその他のすべての型も比較する必要があります。
オブジェクトのサブタイプ
オブジェクトの比較は、まずキーから始めることができます。あるオブジェクトが別のオブジェクトのすべてのキーを含んでいる場合、それはサブタイプである可能性があります。
例えば、キーfoo
を含むObjectA
と、キーfoo
とbar
を含むObjectB
があったとします。この場合、ObjectA
が不正確であれば、ObjectB
はObjectA
のサブタイプである可能性があります。
1type ObjectA = {foo: string, ...};2type ObjectB = {foo: string, bar: number};3
4let objectB: ObjectB = {foo: 'test', bar: 42};5let objectA: ObjectA = objectB; // Works!
しかし、値の型も比較する必要があります。両方のオブジェクトにキーfoo
があり、一方がnumber
で他方がstring
の場合、一方は他方のサブタイプにはなりません。
1type ObjectA = {foo: string, ...};2type ObjectB = {foo: number, bar: number};3
4let objectB: ObjectB = { foo: 1, bar: 2 };5let objectA: ObjectA = objectB; // Error!
5:24-5:30: Cannot assign `objectB` to `objectA` because number [1] is incompatible with string [2] in property `foo`. [incompatible-type]
オブジェクトの値が別のオブジェクトである場合、それらも相互に比較する必要があります。サブタイプであるかどうかを判断できるまで、すべての値を再帰的に比較する必要があります。
関数のサブタイプ
関数のサブタイピングルールはより複雑です。これまで、A
がB
のサブタイプであるのは、B
がA
の取りうるすべての値を含んでいる場合であると見てきました。関数については、この関係がどのように適用されるかは明確ではありません。簡単に言うと、関数型A
は、関数型B
が期待される場所で、型A
の関数が安全に使用できる場合に、関数型B
のサブタイプであると考えることができます。
関数型といくつかの関数があるとします。どの関数が、指定された関数型を期待するコードで安全に使用できるでしょうか?
1type FuncType = (1 | 2) => "A" | "B";2
3declare function f1(1 | 2): "A" | "B" | "C";4declare function f2(1 | null): "A" | "B";5declare function f3(1 | 2 | 3): "A";6
7f1 as FuncType; // Error 8f2 as FuncType; // Error 9f3 as FuncType; // Works!
7:1-7:2: Cannot cast `f1` to `FuncType` because string literal `C` [1] is incompatible with literal union [2] in the return value. [incompatible-cast]8:1-8:2: Cannot cast `f2` to `FuncType` because literal union [1] is incompatible with number literal `2` [2] in the first parameter. [incompatible-cast]
f1
はFuncType
が返さない値を返す可能性があるため、FuncType
に依存するコードはf1
を使用すると安全ではない可能性があります。その型はFuncType
のサブタイプではありません。f2
はFuncType
が行うすべての引数値を処理できないため、FuncType
に依存するコードはf2
を安全に使用できません。その型もFuncType
のサブタイプではありません。f3
はFuncType
が行うすべての引数値を受け入れ、FuncType
が行う値のみを返すため、その型はFuncType
のサブタイプです。
一般的に、関数のサブタイピングルールは次のようになります。関数型B
は、B
の入力がA
のスーパーセットであり、B
の出力がA
のサブセットである場合にのみ、関数型A
のサブタイプです。サブタイプは、親と同じ入力を少なくとも受け入れ、親と同じ出力を最大で返す必要があります。
入力と出力にサブタイピングルールを適用する方向の決定は、変性によって制御されます。これは次のセクションのトピックです。