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

交差型

複数の型のすべてを満たす型を作成することが役立つ場合があります。たとえば、2つの異なるインターフェースを実装する値を受け取る関数を書きたい場合があります。

1interface Serializable {2  serialize(): string;3}4
5interface HasLength {6  length: number;7}8
9function func(value: Serializable & HasLength) {10  // ...11}12
13func({14  length: 3,15  serialize() {16    return '3';17  },18}); // Works19
20func({length: 3}); // Error! Doesn't implement both interfaces
20:6-20:16: Cannot call `func` with object literal bound to `value` because property `serialize` is missing in object literal [1] but exists in `Serializable` [2]. [prop-missing]

交差型構文

交差型は、アンパサンド `&` で連結された任意の数の型です。

Type1 & Type2 & ... & TypeN

複数の行に交差型を分割する場合に役立つ先頭のアンパサンドを追加することもできます。

type Foo =
& Type1
& Type2
& ...
& TypeN

交差型のメンバーは、別の交差型を含む、任意の型にすることができます。

type Foo = Type1 & Type2;
type Bar = Type3 & Type4;

type Baz = Foo & Bar;

交差型は、すべてを満たすが、1つのみ扱う

交差型はユニオン型の反対です。交差型を受け取る関数を呼び出す場合、これらの型のすべてを渡す必要があります。しかし、関数内では、これらの型のいずれか1つとして扱うだけで済みます。

1type A = {a: number, ...};2type B = {b: boolean, ...};3type C = {c: string, ...};4
5function func(value: A & B & C) {6  const a: A = value;7  const b: B = value;8  const c: C = value;9}

値を型の1つとしてのみ扱う場合でも、すべての型を満たしているのでエラーは発生しません。

関数型の交差

交差型の一般的な用途の1つは、渡された入力に基づいて異なる結果を返す関数を表現することです。たとえば、次のような動作をする関数の型を記述したいとします。

  • 値 `"string"` を渡すと文字列を返す。
  • 値 `"number"` を渡すと数値を返す。
  • その他の文字列を渡すと、任意の型 ( `mixed` ) を返す。

この関数の型は次のようになります。

1type Fn =2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);

上記の定義の各行はオーバーロードと呼ばれ、型 `Fn` の関数はオーバーロードされていると言います。

矢印型の周りの括弧の使用に注意してください。これらは、「arrow」コンストラクタよりも交差の優先順位をオーバーライドするために必要です。

オーバーロードされた関数の呼び出し

上記の定義を使用して、次の動作を持つ関数 `fn` を宣言できます。

1declare const fn:2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);5
6const s: string = fn("string"); // Works7const n: number = fn("number"); // Works8const b: boolean = fn("boolean"); // Error!
8:20-8:32: Cannot assign `fn(...)` to `b` because mixed [1] is incompatible with boolean [2]. [incompatible-type]

Flow は、引数の型と、互換性のあるパラメータ型を持つ最初のオーバーロードを照合することで、この動作を実現します。たとえば、引数 `"string"` は、最初のオーバーロードと最後のオーバーロードの両方に一致します。Flow は最初のものを選択します。オーバーロードが一致しない場合、Flow は呼び出しサイトでエラーを発生させます。

オーバーロードされた関数の宣言

同じ関数 `fn` を宣言する同等の方法は、連続した「関数宣言」ステートメントを使用することです。

1declare function fn(x: "string"): string;2declare function fn(x: "number"): number;3declare function fn(x: string): mixed;

Flow の制限事項として、関数の本体を交差型に対してチェックできないことが挙げられます。つまり、上記の宣言の直後に `fn` の次の実装を提供した場合

1function fn(x: mixed) {2  if (x === "string") { return ""; }3  else if (x === "number") { return 0; }4  else { return null; }5}

Flow はそれを黙って受け入れ(`Fn` を推論された型として使用)、このシグネチャに対して実装をチェックしません。そのため、この種の宣言は、実装が省略されるライブラリ定義に適しています。

オブジェクト型の交差

不正確なオブジェクト型の交差を作成する場合、オブジェクトが交差の各メンバーを満たしていると宣言しています。

たとえば、プロパティのセットが異なる2つの不正確なオブジェクトの交差を作成すると、すべてのプロパティを持つオブジェクトになります。

1type One = {foo: number, ...};2type Two = {bar: boolean, ...};3
4type Both = One & Two;5
6const value: Both = {7  foo: 1,8  bar: true9};

同じ名前のプロパティが重複している場合、Flow はオーバーロードされた関数と同様の戦略に従います。つまり、この名前と一致する最初のプロパティの型を返します。

たとえば、`prop` という名前のプロパティを持つ2つの不正確なオブジェクトをマージし、最初のオブジェクトのプロパティ型が `number` で、2番目のオブジェクトのプロパティ型が `boolean` の場合、`prop` にアクセスすると `number` が返されます。

1type One = {prop: number, ...};2type Two = {prop: boolean, ...};3
4declare const both: One & Two;5
6const prop1: number = both.prop; // Works7const prop2: boolean = both.prop; // Error!
7:24-7:32: Cannot assign `both.prop` to `prop2` because number [1] is incompatible with boolean [2]. [incompatible-type]

正確なオブジェクト型を組み合わせるには、代わりにオブジェクト型スプレッドを使用してください。

1type One = {foo: number};2type Two = {bar: boolean};3
4type Both = {5  ...One,6  ...Two,7};8
9const value: Both = {10  foo: 1,11  bar: true12};

注記:オブジェクトに関しては、Flow で実装されている交差型の順序に依存した方法は、集合論的な観点からはしばしば直感に反するように見えることがあります。集合では、交差の被演算子の順序は任意に変更できます(交換法則)。このため、オブジェクト型でのこの種の演算を定義する際には、順序のセマンティクスがより明確に指定されているオブジェクト型スプレッドを使用することをお勧めします。

不可能な交差型

交差型を使用すると、実行時に作成できない型を作成できます。交差型を使用すると、互いに競合する型も含め、任意の型のセットを組み合わせることができます。

たとえば、数値と文字列の交差を作成できます。

1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error!
6func('hi'); // Error!
5:6-5:9: Cannot call `func` with `3.14` bound to `value` because number [1] is incompatible with string [2]. [incompatible-call]
6:6-6:9: Cannot call `func` with `'hi'` bound to `value` because string [1] is incompatible with number [2]. [incompatible-call]

しかし、数値と文字列の両方である値を作成することは不可能ですが、その型を作成することはできます。このような型を作成することに実際的な用途はありませんが、交差型の動作の副作用です。

正確なオブジェクト型の交差を作成すると、誤って不可能な型を作成することがあります。たとえば

1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error!
4func({b: 'hi'}); // Error!
5func({a: 1, b: 'hi'}); // Error!
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]

オブジェクトが正確にプロパティ `a` を持ち、その他のプロパティを持たず、同時に正確にプロパティ `b` を持ち、その他のプロパティを持たないことは不可能です。