メインコンテンツにスキップ

サブセットとサブタイプ

サブタイプとは?

numberbooleanstringのような型は、取りうる値の集合を表します。numberはすべての可能な数値を表すので、単一の数値(例えば42)はnumber型のサブタイプになります。逆に、numberは型42スーパータイプになります。

ある型が別の型のサブタイプであるかどうかを知りたい場合は、両方の型の取りうるすべての値を調べ、もう一方の値のサブセットを持っているかどうかを判断する必要があります。

例えば、1から3までの数値を表すTypeAユニオン型リテラル型の組み合わせ)と、1から5までの数値を表すTypeBがあるとします。TypeATypeBのサブセットであるため、TypeATypeBサブタイプと見なされます。

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と、キーfoobarを含むObjectBがあったとします。この場合、ObjectAが不正確であれば、ObjectBObjectAのサブタイプである可能性があります。

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]

オブジェクトの値が別のオブジェクトである場合、それらも相互に比較する必要があります。サブタイプであるかどうかを判断できるまで、すべての値を再帰的に比較する必要があります。

関数のサブタイプ

関数のサブタイピングルールはより複雑です。これまで、ABのサブタイプであるのは、BAの取りうるすべての値を含んでいる場合であると見てきました。関数については、この関係がどのように適用されるかは明確ではありません。簡単に言うと、関数型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]
  • f1FuncTypeが返さない値を返す可能性があるため、FuncTypeに依存するコードはf1を使用すると安全ではない可能性があります。その型はFuncTypeのサブタイプではありません。
  • f2FuncTypeが行うすべての引数値を処理できないため、FuncTypeに依存するコードはf2を安全に使用できません。その型もFuncTypeのサブタイプではありません。
  • f3FuncTypeが行うすべての引数値を受け入れ、FuncTypeが行う値のみを返すため、その型はFuncTypeのサブタイプです。

一般的に、関数のサブタイピングルールは次のようになります。関数型Bは、Bの入力がAのスーパーセットであり、Bの出力がAのサブセットである場合にのみ、関数型Aのサブタイプです。サブタイプは、親と同じ入力を少なくとも受け入れ、親と同じ出力を最大で返す必要があります。

入力と出力にサブタイピングルールを適用する方向の決定は、変性によって制御されます。これは次のセクションのトピックです。