27
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

flowtypeのUtility Typeについて その2

Last updated at Posted at 2017-01-23

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で見知らぬ型や構文を見つけたときに皆さんはどこを調べていますか?
自分は最初はドキュメントissuePR、果てはstackoverflowなんかを調べていました。
しかし、ドキュメントは更新遅れてるし、issueやPRなんかは量が多くて埋もれるし、Utility Typeの名前によってはノイジーな情報に埋め尽くされて探せたものじゃありませんでした。

そういったときは

tests以下を見ると実際にJSでUtility Typeを利用しているコードが存在するので、そちらを見ると利用イメージが湧きやすく、どんな型なのか分かると思います。
$Abstractとかはなくて泣きそうになったけど)

全くもって初心者に優しくないですが、これなに?となったときはテストを読めの精神ですね!!!

$ReadOnlyArray

$ReadOnlyArrayT型の読み込みのみ可能な配列を表す型です。
そのため、要素への書き込みや、pushpopなどの配列を変更する処理が行えなくなります。

# Try Flow

var arr: $ReadOnlyArray<number> = [1, 2, 3];
arr[0] = 4; // Error !
arr.push(4); // Error !
arr.pop(); // Error !

変更が必要ないところは多いので、これは便利ですね。
余談ですが、Objectでもこういった機能が欲しいなと思ったときにsetter/getterを利用することでReadOnlyObjectを作ることができます。

# Try Flow

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

$TupleMapTに指定したTupleの値をFに指定した関数の型の通り変換する型です。

# Try Flow

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を利用することで、個別に一致した型を変換するということもできます。

# Try Flow

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

$ObjeMapTに指定したObjectの値をFに指定した関数の型の通り変換する型です。

# Try Flow

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

$ObjMapiTに指定したObjectのキーに一致した値をFに指定した関数の型の通り変換する型です。

# Try Flow

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です。
$TaintedTに取った型を受け取れる代わりに、利用をできなくするための型です。

# Try Flow

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 !

上記のように、何でも取れるはずのanymixedに変換することができなくなり、受け取ったTの型としても利用ができなくなります。

$Pred

前回、書き忘れていたUtility Typeその2です。
$Predは述語を取る関数でNumberLiteralに引数の数を指定します。
述語を取る関数というは

# Try Flow

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の結果を呼び出し元のスコープに反映する関数です。

# Try Flow

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です。
$RefineTにベースとなる型を取り、それを変換する$Predの関数をPに、NumberLiteralTの数(※ ここの理解がちょっと怪しい)を取って型を変換する(洗練させる)型になります。

# Try Flow

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は強制的に型を変換するため、

# Try Flow

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時点で存在する$から始まる特殊な型は網羅できたと思います。
ものによっては説明が間違っている可能性もあるので、ここの理解がおかしいよなどあればお気軽にツッコミください。

27
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?