flowtype v0.31.0がでましたね!(既にv0.32.0もでてますけど)
CHANGELOG読んでみると
Added a new "magic" type called $PropertyType. This utility extracts the type of the property 'x' off of the type T.
magic typeなる気になる用語があるじゃありませんか!!
どうやら$から始まる定義済みの型のことを指してるようなのですが、公式のドキュメントや過去のCHANGELOGを見てもそんなものは一切出てきません。
仕方ないので実際にコードを見てみると
type_annotation.mlの中に$から始まる型があるので、名前とコメントから推測しつつ、flowtype.orgのtryで確認しながらどういった型なのか調べてみました。
ちなみにですが、flowtypeのドキュメント的にはUtility Typeというが正しいようです。(2017/01/07追記)
$Either<...T>
$Eitherは|と等価で指定した型のいずれかを表す型(Union type)になります。
type A = {a: number, b: string};
type B = {b: string, c: number};
var a: $Either<A, B> = {a: 1, b: 'hoge'};
var b: $Either<A, B> = {b: 'fuga', c: 2};
var c: $Either<A, B> = {a: 3, b: 'fuga', c: 4};
var d: $Either<A, B> = {a: 5, c: 6}; // Error !!
この例のように$Eitherを利用するとAまたはBと一致する構造のObjectを取ることができます。
flowtypeの仕様としてObjectは部分型になっており、同一のプロパティがあればその型として扱うことができます。
そのため、変数cではAとBに一致しているのためエラーにはなりません。
厳密に型チェックしたいときは後述のExact typeを使用してください。
/* @flow */
type A = {type: 'hoge', num: number};
type B = {type: 'fuga', str: string};
var a: $Either<A, B> = {type: 'hoge', num: 1};
a.type;
a.num; // Error !!
a.str; // Error !!
if (a.type === 'hoge') {
a.num;
a.str; // Error !!
}
if (a.type === 'fuga') {
a.num; // Error !!
a.str;
}
また、実際に利用する場合は型を判別する必要があります。
判別前の場合では変数aに入っている値がAなのか、Bなのか判別できないため、共通しているキー以外はエラーが発生します。
そこで変数aがAかBどちらかの型であることをifを使いDynamic Type Testで判別することで、typeがhogeのAならプロパティnumが利用できるようになり、typeがfugaのBならプロパティstrが利用できるようになります。
$All<...T>
$Allは&と等価で指定した型の全てと一致する型(Intersection Type)になります。
type A = {a: number, b: string};
type B = {b: string, c: number};
var a: $All<A, B> = {a: 1, b: 'hoge'}; // Error !!
var b: $All<A, B> = {b: 'fuga', c: 2}; // Error !!
var c: $All<A, B> = {a: 3, b: 'piyo', c: 4};
var d: $All<A, B> = {a: 5, c: 6}; // Error !!
この例のように$Allを利用するとAとBの両方に一致する構造のObjectを取ることができます。
$Eitherとは違い型の判別は必要ないため、a、b、cのキーはそのまま利用することができます。
type A = {a: number, b: string};
type B = {b: boolean, c: number};
var a: $All<A, B> = {a: 1, b: 'hoge', c: 2};
var b: $All<A, B> = {a: 1, b: true, c: 2}; // Error !!
var c: $All<B, A> = {a: 1, b: true, c: 2};
また、同名のキーがある場合は先に指定したキーの型が優先されます。
$Tuple<...T>
$Tupleは[...T]と等価で要素毎に違う型を指定できる型です。(Array<T>の場合は要素全てがTである必要がある)
~~```js
var a: [number, string, boolean] = [1, 'hoge', true];
var b: [number, string, boolean] = [1, 2, true]; // Error
var c: [number, string, boolean] = [1, 'hoge', 2]; // Error
~~基本的に`Tuple`のような構造を利用するときは名前をつけてオブジェクトにするので使いどころがわかりません。~~
~~[# Try Flow](https://flowtype.org/try/#0PQKgBAAgZgNg9gdzCYAoVA3AhgJzFgLjAG0A7AVwFsAjAUxwBowBnAFxwEtSBzJ6uODFpZSAXTABeEgEYmAcgAWcbrTlN25WqIDc6LADpKWAA4AKUwA8iFGvTAAfFuy7cHYfoOGkAlJIB8YBbeugZGZpbWVHQ4vhIBQdpgwMBgAKI4OHB4AITZqKEm5lZOnDyx8cFJKemZOXkF4cUeQiLlgZXJaRlZYLlAA)~~
~~```js
var a: [number, string, boolean] = [1, 'hoge', true];
a.map((x: number | string | boolean) => x);
a.map((x: number) => x); // Error !!
a.map((x: string) => x); // Error !!
a.map((x: boolean) => x); // Error !!
```~~
~~`[number, string, boolean]`の`Tuple`を`map`で回すときも引数は`number | string | boolean`の型である必要があります。
やっぱり、いまいち利用所が思いつかないです。~~
~~こういう値を返すライブラリで使えるかもしれませんが、そんなライブラリは投げ捨てた方が幸せになれると思います。~~
`$Tuple`は削除されました。`[A, B]`を使いましょう。
# $Supertype<T>
https://flowtype.org/docs/utility-types.html#supertypet
`$Supertype`は`class Klass<-T> {}`と同様に反変を表す型です。
[# Try Flow](https://flowtype.org/try/#0PQKgBAAgZgNg9gdzCYAoVBjGBDAzrsACTgHMBTMAbwF8BuTHfMAMQFcTswyAPAFzIB2AEwLFyVOuix4CAaUa4AjAB4AtABUAfFVRgwUAQApuALjDqAlGYBucAJZCJqaqmvYATmGyKz8mSrEybQBeMAEyJD98RUMLejdPbAAmXwUVNg4Qr0V4jy8AZlT-ZQzsLPDItNjcxIAWIujlQKzsfNowYGAwAFF3dzhPAEJBqQUwKNwk5S0dPQNjM0sbe0caZ1c8gCMfcYUp5rBQit2ZJOqNz02Uk-wpgBIAZVYABzJ3XgBPV5L2MqztmpgTaFG6TH6ZQ5hCKgs5xC5A+ow5SPF5vT7fZr-NodLq9fpDQZAA)
```js
class Hoge {};
class Fuga extends Hoge {};
class Klass1<-T> {
fn(x: T): void {}
}
var a1: Klass1<Hoge> = new Klass1();
var a2: Klass1<Fuga> = a1;
var a3: Klass1<Fuga> = new Klass1();
var a4: Klass1<Hoge> = a3; // Error !!
class Klass2<T> {
fn(x: T): void {}
}
var b1: Klass2<Hoge> = new Klass2();
var b2: Klass2<$Supertype<Fuga>> = b1;
var b3: Klass2<Fuga> = new Klass2();
var b4: Klass2<$Supertype<Hoge>> = b3; // Error !!
-Tの場合には型パラメーターに指定できますが、例えば変数の型や、関数の戻り値には指定できません。
その一方で$Supertypeで型を指定した場合には変数の型などに指定できる代わりに、型パラメーターには指定ができなくなります。
$Subtype
$Subtypeはclass Klass<+T> {}と同様に共変を表す型です。
class Hoge {};
class Fuga extends Hoge {};
class Klass1<K, +V> {
x: { [k: K]: V};
fn(k: K): V {
return this.x[k];
}
}
var a1: Klass1<string, Hoge> = new Klass1();
var a2: Klass1<string, Fuga> = a1; // Error !!
var a3: Klass1<string, Fuga> = new Klass1();
var a4: Klass1<string, Hoge> = a3;
class Klass2<K, V> {
x: { [k: K]: V};
fn(k: K): V {
return this.x[k];
}
}
var b1: Klass2<string, Hoge> = new Klass2();
var b2: Klass2<string, $Subtype<Fuga>> = b1; // Error !!
var b3: Klass2<string, Fuga> = new Klass2();
var b4: Klass2<string, $Subtype<Hoge>> = b3;
$Subtypeも$Supertypeと同様に+Tと$Subtype<T>で指定できるところが違います。
$Type
$TypeはTと一致した型を取る型です。
class A { }
class B extends A { }
class C extends B { }
var a1: $Type<B> = A; // Error!
var a2: $Type<B> = B;
var a3: $Type<B> = C; // Error!
var b1: typeof B = A; // Error!
var b2: typeof B = B;
var b3: typeof B = C;
var c1: Class<B> = A; // Error!
var c2: Class<B> = B;
var c3: Class<B> = C;
似たようなものでtypeofやClass<T>もありますが、それらと違い$Typeが厳密に一致する型をとります。
そのため、継承関係にある子のクラスをTに渡したさいに、typeofやClass<T>では通りますが、$Typeではエラーとなります。
$PropertyType
$PropertyTypeはTに指定した型のプロパティxの型を取得する型です。
type A = {x: number};
var a: $PropertyType<A, 'x'> = 1;
var a: $PropertyType<A, 'x'> = 'aaa'; // Error !!
ここでプロパティ名に指定できるのはString literalのみになるため、String literalのUnion typeなどは指定できません。
そのため、残念ながら後述の$Keysと組み合わせて、オブジェクトのプロパティのUnion typeを作ることはできません。
$NonMaybeType
$NonMaybeTypeはTに指定したMaybe Typeをnullやundefinedを許可しない元の型に戻す型です。
type A = ?string;
var a1: A = 'hoge';
var a2: A = null;
var b1: $NonMaybeType<A> = 'fuga';
var b2: $NonMaybeType<A> = null; // Error !!
$Shape
$ShapeはTに指定した型の部分型であり、Objectとは違いTと一致しないプロパティを許容しない型になります。
type A = {a: number, b: string, c: boolean};
var a: $Shape<A> = {a: 1, b: 'hoge', c: true};
var b: $Shape<A> = {a: 1};
var c: $Shape<A> = {b: 'hoge'};
var d: $Shape<A> = {c: true};
var e: $Shape<A> = {d: 1.1}; // Error !!
ただし、Union type、Intersection type、後述のDiff typeと組み合わせると、それぞれで許容される型が変わります。
type A = {a: number, b: string};
type B = {b: string, c: boolean};
// Unon type
var a00: $Shape<A | B> = {};
var a01: $Shape<A | B> = {a: 1}; // Error !!
var a02: $Shape<A | B> = {b: 'hoge'};
var a03: $Shape<A | B> = {c: true}; // Error !!
var a04: $Shape<A | B> = {d: 1.1}; // Error !!
var a05: $Shape<A | B> = {a: 1, b: 'hoge'}; // Error !!
var a06: $Shape<A | B> = {a: 1, c: true}; // Error !!
var a07: $Shape<A | B> = {a: 1, d: 1.1}; // Error !!
var a08: $Shape<A | B> = {b: 'hoge', c: true}; // Error !!
var a09: $Shape<A | B> = {b: 'hoge', d: 1.1}; // Error !!
var a10: $Shape<A | B> = {a: 1, b: 'hoge', c: true}; // Error !!
var a11: $Shape<A | B> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var a12: $Shape<A | B> = {a: 1, c: true, d: 1.1}; // Error !!
var a13: $Shape<A | B> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Intersection type
var b00: $Shape<A & B> = {};
var b01: $Shape<A & B> = {a: 1};
var b02: $Shape<A & B> = {b: 'hoge'};
var b03: $Shape<A & B> = {c: true};
var b04: $Shape<A & B> = {d: 1.1}; // Error !!
var b05: $Shape<A & B> = {a: 1, b: 'hoge'};
var b06: $Shape<A & B> = {a: 1, c: true};
var b07: $Shape<A & B> = {a: 1, d: 1.1}; // Error !!
var b08: $Shape<A & B> = {b: 'hoge', c: true};
var b09: $Shape<A & B> = {b: 'hoge', d: 1.1}; // Error !!
var b10: $Shape<A & B> = {a: 1, b: 'hoge', c: true};
var b11: $Shape<A & B> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var b12: $Shape<A & B> = {a: 1, c: true, d: 1.1}; // Error !!
var b13: $Shape<A & B> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Diff type
var c00: $Shape<$Diff<A, B>> = {};
var c01: $Shape<$Diff<A, B>> = {a: 1}; // Error !!
var c02: $Shape<$Diff<A, B>> = {b: 'hoge'}; // Error !!
var c03: $Shape<$Diff<A, B>> = {c: true}; // Error !!
var c04: $Shape<$Diff<A, B>> = {d: 1.1}; // Error !!
var c05: $Shape<$Diff<A, B>> = {a: 1, b: 'hoge'}; // Error !!
var c06: $Shape<$Diff<A, B>> = {a: 1, c: true}; // Error !!
var c07: $Shape<$Diff<A, B>> = {a: 1, d: 1.1}; // Error !!
var c08: $Shape<$Diff<A, B>> = {b: 'hoge', c: true}; // Error !!
var c09: $Shape<$Diff<A, B>> = {b: 'hoge', d: 1.1}; // Error !!
var c10: $Shape<$Diff<A, B>> = {a: 1, b: 'hoge', c: true}; // Error !!
var c11: $Shape<$Diff<A, B>> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var c12: $Shape<$Diff<A, B>> = {a: 1, c: true, d: 1.1}; // Error !!
var c13: $Shape<$Diff<A, B>> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
Union typeの場合は空オブジェクトとAとBの共通部分の部分型に。
Intersection typeの場合は空オブジェクトとAとBの部分型に。
Diff typeの場合は空オブジェクトのみを許容する形になります。
正直、Diff typeのパターンはバグだと思います。変数c01のパターンは許容されるべきでは・・・。
$Diff
$DiffはTからSを除外した型を許容する型(Diff type)になります。
type A = {a: number, b: string};
type B = {b: string, c: boolean};
var a: $Diff<A, B> = {a: 1};
var c: $Diff<A, B> = {a: 1, b: 'hoge'};
var d: $Diff<A, B> = {a: 1, c: true};
var e: $Diff<A, B> = {b: 'hoge', c: true}; // Error !!
実際にどう使うかは悩ましいですが、$Diffがあることで追加だけでなく除外できるようになるため、型の表現力が格段に上がります。
$Keys | $Enum
$KeysはTに指定した型のキーのUnion typeになります。
type A = {a: number, b: string, c: boolean};
var a: $Keys<A> = 'a';
var b: $Keys<A> = 'b';
var c: $Keys<A> = 'c';
var d: $Keys<A> = 'd'; // Error !!
この場合、$Keys<A>は'a' | 'b' | 'c'と等価になります。
$Exact
$ExactはTに指定した型の同型のみを許容する型になります。
v0.3.2からは構文が導入され{| |}で表現可能になっています。
type A = {a: number, b: string, c: boolean};
var a: $Exact<A> = {a: 1, b: 'hoge', c: true};
var b: $Exact<A> = {a: 1, b: 'hoge'}; // Error !!
var c: $Exact<A> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
$Exactは$Shapeと違い、Union type、Intersection type、Diff typeと組み合わせると代入不可能な型になります。
type A = {a: number, b: string};
type B = {b: string, c: boolean};
// Unon type
var a00: $Exact<A | B> = {}; // Error !!
var a01: $Exact<A | B> = {a: 1}; // Error !!
var a02: $Exact<A | B> = {b: 'hoge'}; // Error !!
var a03: $Exact<A | B> = {c: true}; // Error !!
var a04: $Exact<A | B> = {d: 1.1}; // Error !!
var a05: $Exact<A | B> = {a: 1, b: 'hoge'}; // Error !!
var a06: $Exact<A | B> = {a: 1, c: true}; // Error !!
var a07: $Exact<A | B> = {a: 1, d: 1.1}; // Error !!
var a08: $Exact<A | B> = {b: 'hoge', c: true}; // Error !!
var a09: $Exact<A | B> = {b: 'hoge', d: 1.1}; // Error !!
var a10: $Exact<A | B> = {a: 1, b: 'hoge', c: true}; // Error !!
var a11: $Exact<A | B> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var a12: $Exact<A | B> = {a: 1, c: true, d: 1.1}; // Error !!
var a13: $Exact<A | B> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Intersection type
var b00: $Exact<A & B> = {}; // Error !!
var b01: $Exact<A & B> = {a: 1}; // Error !!
var b02: $Exact<A & B> = {b: 'hoge'}; // Error !!
var b03: $Exact<A & B> = {c: true}; // Error !!
var b04: $Exact<A & B> = {d: 1.1}; // Error !!
var b05: $Exact<A & B> = {a: 1, b: 'hoge'}; // Error !!
var b06: $Exact<A & B> = {a: 1, c: true}; // Error !!
var b07: $Exact<A & B> = {a: 1, d: 1.1}; // Error !!
var b08: $Exact<A & B> = {b: 'hoge', c: true};
var b09: $Exact<A & B> = {b: 'hoge', d: 1.1}; // Error !!
var b10: $Exact<A & B> = {a: 1, b: 'hoge', c: true};
var b11: $Exact<A & B> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var b12: $Exact<A & B> = {a: 1, c: true, d: 1.1}; // Error !!
var b13: $Exact<A & B> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Diff type
var c00: $Exact<$Diff<A, B>> = {}; // Error !!
var c01: $Exact<$Diff<A, B>> = {a: 1}; // Error !!
var c02: $Exact<$Diff<A, B>> = {b: 'hoge'}; // Error !!
var c03: $Exact<$Diff<A, B>> = {c: true}; // Error !!
var c04: $Exact<$Diff<A, B>> = {d: 1.1}; // Error !!
var c05: $Exact<$Diff<A, B>> = {a: 1, b: 'hoge'}; // Error !!
var c06: $Exact<$Diff<A, B>> = {a: 1, c: true}; // Error !!
var c07: $Exact<$Diff<A, B>> = {a: 1, d: 1.1}; // Error !!
var c08: $Exact<$Diff<A, B>> = {b: 'hoge', c: true}; // Error !!
var c09: $Exact<$Diff<A, B>> = {b: 'hoge', d: 1.1}; // Error !!
var c10: $Shape<$Diff<A, B>> = {a: 1, b: 'hoge', c: true}; // Error !!
var c11: $Exact<$Diff<A, B>> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var c12: $Exact<$Diff<A, B>> = {a: 1, c: true, d: 1.1}; // Error !!
var c13: $Exact<$Diff<A, B>> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
これはUnion typeの場合はAもしくはBを満たす型がなくなり。
Intersection type、Diff typeではExactがサポートされていないためエラーになります。
(Intersection typeではexact type. Type is incompatible with (unclassified use type: MakeExactT) any member of intersection typeというエラーが発生し、Diff typeではexact type. Unsupported exact typeが発生する)
また、Exact type同士でUnion type、Intersection type、Diff typeを作成すると上記までの結果と変わります。
type A = {| a: number, b: string |};
type B = {| b: string, c: boolean |};
// Unon type
var a00: A | B = {}; // Error !!
var a01: A | B = {a: 1}; // Error !!
var a02: A | B = {b: 'hoge'}; // Error !!
var a03: A | B = {c: true}; // Error !!
var a04: A | B = {d: 1.1}; // Error !!
var a05: A | B = {a: 1, b: 'hoge'};
var a06: A | B = {a: 1, c: true}; // Error !!
var a07: A | B = {a: 1, d: 1.1}; // Error !!
var a08: A | B = {b: 'hoge', c: true};
var a09: A | B = {b: 'hoge', d: 1.1}; // Error !!
var a10: A | B = {a: 1, b: 'hoge', c: true}; // Error !!
var a11: A | B = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var a12: A | B = {a: 1, c: true, d: 1.1}; // Error !!
var a13: A | B = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Intersection type
var b00: A & B = {}; // Error !!
var b01: A & B = {a: 1}; // Error !!
var b02: A & B = {b: 'hoge'}; // Error !!
var b03: A & B = {c: true}; // Error !!
var b04: A & B = {d: 1.1}; // Error !!
var b05: A & B = {a: 1, b: 'hoge'}; // Error !!
var b06: A & B = {a: 1, c: true}; // Error !!
var b07: A & B = {a: 1, d: 1.1}; // Error !!
var b08: A & B = {b: 'hoge', c: true}; // Error !!
var b09: A & B = {b: 'hoge', d: 1.1}; // Error !!
var b10: A & B = {a: 1, b: 'hoge', c: true}; // Error !!
var b11: A & B = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var b12: A & B = {a: 1, c: true, d: 1.1}; // Error !!
var b13: A & B = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
// Diff type
var c00: $Diff<A, B> = {}; // Error !!
var c01: $Diff<A, B> = {a: 1}; // Error !!
var c02: $Diff<A, B> = {b: 'hoge'}; // Error !!
var c03: $Diff<A, B> = {c: true}; // Error !!
var c04: $Diff<A, B> = {d: 1.1}; // Error !!
var c05: $Diff<A, B> = {a: 1, b: 'hoge'}; // Error !!
var c06: $Diff<A, B> = {a: 1, c: true}; // Error !!
var c07: $Diff<A, B> = {a: 1, d: 1.1}; // Error !!
var c08: $Diff<A, B> = {b: 'hoge', c: true}; // Error !!
var c09: $Diff<A, B> = {b: 'hoge', d: 1.1}; // Error !!
var c10: $Diff<A, B> = {a: 1, b: 'hoge', c: true}; // Error !!
var c11: $Diff<A, B> = {a: 1, b: 'hoge', d: 1.1}; // Error !!
var c12: $Diff<A, B> = {a: 1, c: true, d: 1.1}; // Error !!
var c13: $Diff<A, B> = {a: 1, b: 'hoge', c: true, d: 1.1}; // Error !!
Union typeではExact typeのAもしくはBと一致するので変数a5、a8でエラーが出ません。
しかし、Intersection typeではA、B共にExact typeのため、それぞれに定義されたプロパティを持つことができません。
そのためAから見るとBのプロパティcが余分になるためエラーとなり、Bから見たときは逆にプロパティaが余分になりエラーとなってしまいます。
Diff typeの場合はよくわかりませんが、Exact typeなので除くということが出来ないということだと思います。(たぶん)
$Exports<'M'>
$Exportsはimportやrequireで読み込まれるモジュールの型です。
declare module M {
declare class Hoge {}
declare class Fuga {}
}
var a: $Exports<'M'> = require('M');
var a1: string = a.Hoge; // Error !!
var a2: string = a.Fuga; // Error !!
var a3: string = a.Piyo;
※ 実際にはMというモジュールは作らないとrequireでもエラーは発生します
少し挙動は特殊で、モジュールに定義されたHogeとFugaは定義通りの型となり、違う型の変数に代入するとエラーになります。
しかし、未定義のPiyoはany扱いで読み込まれるためエラーにはなりません。
このあたりの挙動は
-
/* @flow */付きのファイルのモジュールを読み込む -
/* @flow */のないファイルのモジュール読み込む -
declare moduleで定義されたモジュールを読み込む - 存在しないファイルを読み込む
の組み合わせ方で挙動が変わります。
まぁ、普通にimportを使っていれば$Exportsは使わないので、そのあたりの検証は割愛します。
$Abstract
$AbstractはT型を抽象型に変換する型です。
class A<T> {
static a: T;
}
class B<T> {
static a: $Abstract<T>;
}
A.a.b.c;
B.a.b.c; // Error !
上記のように型パラメーターを取るクラスで、staticなプロパティに受け取った型パラメーターを指定すると、パラメータを渡さずに呼び出したさいにそのプロパティはany(もしくはunknown)として判定されてしまいます。
しかし、それでは困るといった場合に直接のアクセスを防ぐために$Abstractを使用して、アクセスできないように抽象型に変換します。
これはReactの型定義を書くときに利用するのが一般的な使い方のようです。
おわりに
magic typeはおもしろいですね!!
最近、JSらしいコードを残しながら型安全を目指すにはどうすれば良いんだろ・・・?と思っていたのですが、$Either、$All、$Diff、$Shape、$ExactとObjectを操作する型の充実ぶりを見るとこれだ!!という感じがあります。
表現力高いし、推論もなかなか強く、JSのエコシステムにそのまま乗れて便利なので、もっとflowtype人口が増えてくれればいいのに。
--