flowtypeのv0.38.0がでましたね!
CHANGELOGを読んでみると
$ReadOnlyArray is the common supertype of arrays and tuples. It's basically an array without stuff like push(), pop(), etc
$ReadOnlyArray<T>
なんて便利そうなUtility Type(旧Magic Type)が追加されてるじゃないですか!!
これは楽しそうだと
を見てみるとなんか$
から始まる見知らぬ型が増えてる・・・。
ドキュメントにないのはまだ許せるけど、CHANGELOGにすらないってどういうことなの・・・!!!
嘆いても仕方ないので、気合を新たに調べ直してみました。
ちなみに、その他のUtility Typeについては下記を参考にしてください。
また、Utility Typeの調べ方がわかったので$Type
や$Abstract
などよくわからなかった型についてもその1にて更新してあります。
Utility Typeの調べ方
flowtypeで見知らぬ型や構文を見つけたときに皆さんはどこを調べていますか?
自分は最初はドキュメントやissue、PR、果てはstackoverflowなんかを調べていました。
しかし、ドキュメントは更新遅れてるし、issueやPRなんかは量が多くて埋もれるし、Utility Typeの名前によってはノイジーな情報に埋め尽くされて探せたものじゃありませんでした。
そういったときは
のtests
以下を見ると実際にJSでUtility Typeを利用しているコードが存在するので、そちらを見ると利用イメージが湧きやすく、どんな型なのか分かると思います。
($Abstract
とかはなくて泣きそうになったけど)
全くもって初心者に優しくないですが、これなに?となったときはテストを読めの精神ですね!!!
$ReadOnlyArray
$ReadOnlyArray
はT
型の読み込みのみ可能な配列を表す型です。
そのため、要素への書き込みや、push
やpop
などの配列を変更する処理が行えなくなります。
var arr: $ReadOnlyArray<number> = [1, 2, 3];
arr[0] = 4; // Error !
arr.push(4); // Error !
arr.pop(); // Error !
変更が必要ないところは多いので、これは便利ですね。
余談ですが、Object
でもこういった機能が欲しいなと思ったときにsetter/getterを利用することでReadOnlyObjectを作ることができます。
type ReadOnlyObject = {
get a(): number;
get b(): string;
get c(): boolean;
};
var obj: ReadOnlyObject = {a: 1, b: 'abc', c: true};
obj.a.toFixed();
obj.a.replace('', ''); // Error !
obj.a = 2; // Error !
とはいえ、ちょっとイケてない感はあるので、flowtypeにてReadOnlyObjectを対応して欲しいものですね。
$TupleMap
$TupleMap
はT
に指定したTupleの値をF
に指定した関数の型の通り変換する型です。
declare var _: [Array<number>, Array<string>, Array<boolean>];
type A = $TupleMap<typeof(_), <K>(k:Array<K>) => K>;
declare var a: A;
a[0].toFixed();
a[0].replace('', ''); // Error !!
a[1].toFixed(); // Error !!
a[1].replace('', '');
a[2].toFixed(); // Error !!
a[2].replace('', ''); // Error !!
上記、サンプルでは各要素のArray<*>
な型を剥いて内部の型に変換しています。
このように一括で型を剥いたり、被せたりもしますがIntersection Typeを利用することで、個別に一致した型を変換するということもできます。
declare var _: [number, string, boolean];
type A = $TupleMap<typeof(_), ((k:number)=>string)&((k:string)=>boolean)>;
declare var a: A;
a[0].toFixed(); // Error !!
a[0].replace('', '');
a[1].toFixed(); // Error !!
a[1].replace('', ''); // Error !!
a[2].toFixed(); // Error !!
a[2].replace('', ''); // Error !!
このとき、a[2]
では対応する型変換の関数がIntersection Typeで定義されていないためエラーとなっています。
そのため、$TupleMap
を使うときは全ての要素を処理できる関数を型変換の関数として定義してあげる必要があります。
これは、後述の$ObjMap
、$ObjMapi
でも同様の挙動になります。
$ObjMap
$ObjeMap
はT
に指定したObjectの値をF
に指定した関数の型の通り変換する型です。
declare var _: {a: Array<number>, b: Array<string>, c: Array<boolean>};
type A = $ObjMap<typeof(_), <K>(k:Array<K>) => K>;
declare var a: A;
a.a.toFixed();
a.a.replace('', ''); // Error !
a.b.toFixed(); // Error !
a.b.replace('', '');
a.c.toFixed(); // Error !
a.c.replace('', ''); // Error !
$TupleMap
のObject版という形で同様の書き方で変換を行います。
$ObjMapi
$ObjMapi
はT
に指定したObjectのキーに一致した値をF
に指定した関数の型の通り変換する型です。
declare var _: {FOO: number, BAR: number};
type A = $ObjMapi<typeof(_), (<K>(k:'FOO') => string)&(<K>(k:'BAR') => Array<*>)>;
declare var a: A;
a.FOO.toFixed(); // Error !
a.FOO.replace('', '');
a.BAR.toFixed(); // Error !
a.BAR.join('');
上記のようにFOO
に一致したキーの型をstringに、BAR
に一致したキーの型をArray<*>に変換しています。
$ObjMapi
も$TupleType
や$ObjMap
と同様にIntersection Typeを使わずに全てのキーに一致する(つまりstringに一致するキー)値を変換するということができます。
$Tainted
前回、書き忘れていたUtility Typeです。
$Tainted
はT
に取った型を受け取れる代わりに、利用をできなくするための型です。
var t1: $Tainted<number> = 1;
var t2: any = t1; // Error !
var t3: mixed = t1; // Error !
var t4: $Tainted<number> = t1;
console.log(t1); // Error !
t1.toFixed(); // Error !
上記のように、何でも取れるはずのany
やmixed
に変換することができなくなり、受け取ったT
の型としても利用ができなくなります。
$Pred
前回、書き忘れていたUtility Typeその2です。
$Pred
は述語を取る関数でNumberLiteral
に引数の数を指定します。
述語を取る関数というは
declare function isString1(x: mixed): boolean %checks(typeof x === 'string');
function isString2(x: mixed): boolean %checks {
return typeof x === 'string';
}
declare var a: mixed;
if (isString1(a)) {
a.replace('', '');
}
if (isString2(a)) {
a.replace('', '');
}
のように戻り値に%checks
という構文を取り、関数で行ったDynamic Type Testingの結果を呼び出し元のスコープに反映する関数です。
declare function isString1(x: mixed): boolean %checks(typeof x === 'string');
function isString2(x: mixed): boolean %checks {
return typeof x === 'string';
}
var fn1: $Pred<1> = isString1;
var fn2: $Pred<1> = isString2;
var fn3: $Pred<2> = isString2; // Error !
var fn4: $Pred<2> = (a: number, b: number) => a + b; // Error !
$Pred
はそういった述語のある関数を取るための専用の型になります。
$Refine
前回、書き忘れていたUtility Typeその3です。
$Refine
はT
にベースとなる型を取り、それを変換する$Pred
の関数をP
に、NumberLiteral
にT
の数(※ ここの理解がちょっと怪しい)を取って型を変換する(洗練させる)型になります。
declare function refine<T, P: $Pred<1>>(v: T, cb: P): $Refine<T,P,1>;
declare function isString(x: mixed): boolean %checks(typeof x === "string");
declare var a: mixed;
var b = refine(a, isString);
b.replace('', '')
ちなみに、$Refine
は強制的に型を変換するため、
declare function refine<T, P: $Pred<1>>(v: T, cb: P): $Refine<T,P,1>;
declare function isString(x: mixed): boolean %checks(typeof x === "string");
var a: mixed = 1;
var b = refine(a, isString);
b.replace('', '')
b.toFixed(); // Error !
のように変数a
にstring以外の型を入れても、変数b
ではstringとして扱われます。
何かに使えそうな感じはあるのですが。どういったときに使えば良いのかなかなか悩ましいです。
おわりに
いかがでしたでしょうか。これでv0.38.0時点で存在する$
から始まる特殊な型は網羅できたと思います。
ものによっては説明が間違っている可能性もあるので、ここの理解がおかしいよなどあればお気軽にツッコミください。