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

ジェネリクス

ジェネリクス(多相型と呼ばれることもあります)は、型を抽象化する手段です。

渡された値をそのまま返す`identity`関数を記述することを想像してみてください。

function identity(value) {
return value;
}

この関数はあらゆる型を扱うため、具体的な型を記述しようとすると多くの問題に直面します。

1function identity(value: string): string {2  return value;3}

代わりに、関数にジェネリック(または多相型)を作成し、他の型の代わりに使用できます。

1function identity<T>(value: T): T {2  return value;3}

ジェネリクスは、関数、関数型、クラス、型エイリアス、インターフェースで使用できます。

警告:Flowはジェネリック型を推論しません。ジェネリック型を持つようにしたい場合は、**アノテーションを付ける**必要があります。 そうしないと、Flowは期待するよりも多相性の低い型を推論する可能性があります。

ジェネリックスの構文

構文中にジェネリック型が登場する場所はいくつかあります。

ジェネリクスを使用した関数

関数は、関数パラメータリストの前に型パラメータリスト`<T>`を追加することでジェネリクスを作成できます。

ジェネリクスは、関数(パラメータまたは戻り値の型)に他の型を追加する場所と同じ場所で使用する事ができます。

1function method<T>(param: T): T {2  return param;3}4
5const f = function<T>(param: T): T {6  return param;7}

ジェネリクスを使用した関数型

関数型は、通常の関数と同様に、関数型パラメータリストの前に型パラメータリスト`<T>`を追加することでジェネリクスを作成できます。

ジェネリクスは、関数型(パラメータまたは戻り値の型)に他の型を追加する場所と同じ場所で使用する事ができます。

<T>(param: T) => T

そしてそれは独自の型として使用されます。

1function method(func: <T>(param: T) => T) {2  // ...3}

ジェネリクスを使用したクラス

クラスは、クラスの本体の前に型パラメータリストを配置することでジェネリクスを作成できます。

1class Item<T> {2  // ...3}

ジェネリクスは、クラス(プロパティ型とメソッドのパラメータ/戻り値の型)に他の型を追加する場所と同じ場所で使用する事ができます。

1class Item<T> {2  prop: T;3
4  constructor(param: T) {5    this.prop = param;6  }7
8  method(): T {9    return this.prop;10  }11}

ジェネリクスを使用した型エイリアス

1type Item<T> = {2  foo: T,3  bar: T,4};

ジェネリクスを使用したインターフェース

1interface Item<T> {2  foo: T,3  bar: T,4}

呼び出し可能オブジェクトへの型引数の指定

呼び出し可能なエンティティに、呼び出し時に直接ジェネリクスの型引数を指定できます。

1function doSomething<T>(param: T): T {2  // ...3  return param;4}5
6doSomething<number>(3);

ジェネリッククラスにも、`new`式で直接型引数を指定できます。

1class GenericClass<T> {}2const c = new GenericClass<number>();

型引数のいくつかだけを指定したい場合は、`_`を使用してFlowに型を推論させることができます。

1class GenericClass<T, U=string, V=number>{}2const c = new GenericClass<boolean, _, string>();

**警告:** パフォーマンスのため、可能な限り具体的な引数でアノテーションすることを常に推奨します。 `_`は安全ではありませんが、型引数を明示的に指定するよりも遅くなります。

ジェネリックスの動作

ジェネリクスは変数のように動作する

ジェネリック型は、型に使用される点を除いて、変数や関数パラメータと非常によく似ています。スコープ内にあるときはいつでも使用できます。

1function constant<T>(value: T): () => T {2  return function(): T {3    return value;4  };5}

必要な数のジェネリクスを作成する

型パラメータリストには、必要な数のジェネリクスを含めることができ、任意の名前を付けることができます。

1function identity<One, Two, Three>(one: One, two: Two, three: Three) {2  // ...3}

ジェネリクスは値を追跡する

値にジェネリック型を使用する場合、Flowは値を追跡し、別のものと置き換えていないことを確認します。

1function identity<T>(value: T): T {2  return "foo"; // Error!
3}4 5function identity<T>(value: T): T {
6 value = "foo"; // Error!
7 return value; // Error!
8}
2:10-2:14: Cannot return `"foo"` because string [1] is incompatible with `T` [2]. [incompatible-return]
5:10-5:17: Cannot declare `identity` [1] because the name is already bound. [name-already-bound]
6:11-6:15: Cannot assign `"foo"` to `value` because string [1] is incompatible with `T` [2]. [incompatible-type]
7:10-7:14: Cannot return `value` because string [1] is incompatible with `T` [2]. [incompatible-return]

Flowは、ジェネリックを通して渡される値の具体的な型を追跡し、後でそれを使用できるようにします。

1function identity<T>(value: T): T {2  return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: 3 = identity(42); // Error
7:16-7:27: Cannot assign `identity(...)` to `three` because number [1] is incompatible with number literal `3` [2]. [incompatible-type]

ジェネリクスへの型の追加

`mixed`と同様に、ジェネリクスには「不明」型があります。 ジェネリックを特定の型であるかのように使用することは許可されていません。

1function logFoo<T>(obj: T): T {2  console.log(obj.foo); // Error!
3 return obj;4}
2:19-2:21: Cannot get `obj.foo` because property `foo` is missing in mixed [1]. [incompatible-use]

型を絞り込むことができますが、ジェネリックは依然としてあらゆる型の受け入れを許可します。

1function logFoo<T>(obj: T): T {2  if (obj && obj.foo) {3    console.log(obj.foo); // Works.4  }5  return obj;6}7
8logFoo({ foo: 'foo', bar: 'bar' });  // Works.9logFoo({ bar: 'bar' }); // Works. :(

代わりに、関数パラメータと同様に、ジェネリックに型を追加できます。

1function logFoo<T: {foo: string, ...}>(obj: T): T {2  console.log(obj.foo); // Works!3  return obj;4}5
6logFoo({ foo: 'foo', bar: 'bar' });  // Works!7logFoo({ bar: 'bar' }); // Error!
7:8-7:21: Cannot call `logFoo` because property `foo` is missing in object literal [1] but exists in object type [2] in type argument `T`. [prop-missing]

このようにして、特定の型のみを使用することを許可しながら、ジェネリックスの動作を維持できます。

1function identity<T: number>(value: T): T {2  return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: "three" = identity("three"); // Error!
7:31-7:37: Cannot call `identity` because string [1] is incompatible with number [2] in type argument `T`. [incompatible-call]

ジェネリック型は境界として機能する

1function identity<T>(val: T): T {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Works!

Flowでは、多くの場合、ある型を別の型に渡すと元の型が失われます。そのため、特定の型をより非特定の型に渡すと、Flowはそれがかつてより具体的な何かであったことを「忘れる」のです。

1function identity(val: string): string {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Error!
6:18-6:32: Cannot assign `identity(...)` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]

ジェネリクスを使用すると、制約を追加しながら、より具体的な型を保持できます。このように、ジェネリックスの型は「境界」として機能します。

1function identity<T: string>(val: T): T {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Works!

境界を持つジェネリック型を持つ値がある場合、それをより具体的な型であるかのように使用することはできないことに注意してください。

1function identity<T: string>(val: T): T {2  let str: string = val; // Works!3  let bar: 'bar'  = val; // Error!
4 return val;5}6 7identity('bar');
3:21-3:23: Cannot assign `val` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]

パラメーター化されたジェネリクス

ジェネリクスでは、関数への引数のように型を渡せる場合があります。これらはパラメーター化されたジェネリクス(またはパラメトリック多相)として知られています。

たとえば、ジェネリックを持つ型エイリアスはパラメーター化されています。使用するときは、型引数を指定する必要があります。

1type Item<T> = {2  prop: T,3}4
5let item: Item<string> = {6  prop: "value"7};

これは、関数に引数を渡すようなものですが、戻り値は使用できる型です。

クラス(型として使用する場合)、型エイリアス、インターフェースはすべて、型引数を渡す必要があります。関数と関数型には、パラメーター化されたジェネリクスはありません。

クラス

1class Item<T> {2  prop: T;3  constructor(param: T) {4    this.prop = param;5  }6}7
8let item1: Item<number> = new Item(42); // Works!9let item2: Item = new Item(42); // Error!
9:12-9:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]

型エイリアス

1type Item<T> = {2  prop: T,3};4
5let item1: Item<number> = { prop: 42 }; // Works!6let item2: Item = { prop: 42 }; // Error!
6:12-6:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]

インターフェース

1interface HasProp<T> {2  prop: T,3}4
5class Item {6  prop: string;7}8
9Item.prototype as HasProp<string>; // Works!10Item.prototype as HasProp; // Error!
10:19-10:25: Cannot use `HasProp` [1] without 1 type argument. [missing-type-arg]

パラメーター化されたジェネリクスへのデフォルトの追加

関数のパラメータと同様に、パラメーター化されたジェネリクスにもデフォルトを指定できます。

1type Item<T: number = 1> = {2  prop: T,3};4
5let foo: Item<> = { prop: 1 };6let bar: Item<2> = { prop: 2 };

型を使用する場合は、常に角括弧`<>`を含める必要があります(関数呼び出しでは括弧と同様です)。

分散シジル

分散シジルを使用して、ジェネリックスのサブタイピング動作を指定することもできます。デフォルトでは、ジェネリクスは不変に動作しますが、宣言に`+`を追加して共変的に動作させたり、`-`を追加して反変的に動作させたりできます。Flowにおける分散に関するドキュメントで、Flowにおける分散の詳細情報をご覧ください。

分散シジルを使用すると、ジェネリックスの使用目的をより具体的に指定でき、Flowにより正確な型チェックを行うことができます。たとえば、次の関係を維持したい場合があります。

1type GenericBox<+T> = T;2
3const x: GenericBox<number> = 3;4x as GenericBox<number| string>;

上記の例は、`+`分散シジルなしでは実現できません。

1type GenericBoxError<T> = T;2
3const x: GenericBoxError<number> = 3;4x as GenericBoxError<number| string>; // number | string is not compatible with number.
4:1-4:1: Cannot cast `x` to `GenericBoxError` because string [1] is incompatible with number [2] in type argument `T` [3]. [incompatible-cast]

ジェネリックに分散シジルでアノテーションを付けると、Flowはそれらの型がその分散シジルにとって意味のある位置にのみ出現することを確認します。たとえば、ジェネリック型パラメータを共変的に動作するように宣言し、反変の位置で使用することはできません。

1type NotActuallyCovariant<+T> = (T) => void;
1:34-1:34: Cannot use `T` [1] in an input position because `T` [1] is expected to occur only in output positions. [incompatible-variance]