久しぶりにFlowtypeを触ってみると型の表現力が上がってました。
Changelogを見ると他にもいろいろ遊びがいがある変更が入っているようです。(Changelogにしか載ってない情報とかあるので必読です)
これはもうPhantom Typeというカッコいい名前の型で遊ぶしかないですね!!
Phantom Type
とりあえず実際に使うことが出来るのか見るために、上記の幽霊型の例をFlowtypeに落とし込みます。
class A<T> {
x: number;
constructor(x: number) {
this.x = x;
}
}
var res0: A<string> = new A(3);
var res1: A<Array<string>> = new A(3);
var a: A<string> = res0;
var b: A<string> = res1; // Error
上記のコードでFlowtypeでcheckすると
var a: A<string> = res0;
では型が一致するのでcheckが通りますが、
var b: A<string> = res1; // Error
では型が一致しないのでcheckでエラーが発生します。
実際にはA<T>
のT
は使用していないのでA
型として動いてくれます。いい感じですね!!
とはいえ、これだけだとあまりおもしろさが分からないので、もう少しJSで使えそうな実用例で遊んでみます。
JSでの実用例(理想)
JSでイベントを扱うときにイベント名に対して、コールバックで受け取る引数は大抵決まっています。
しかし、この手の型指定は大抵でイベント名をstring
、コールバックをFunction
として引数の型チェックをしないことが多いです。
これだとせっかく型を扱うのに静的にチェックできないとか意味がないよね・・・ということでPhantom Typeでイベント名に対応した引数をチェックできるようにします。
type Event = { type: string; };
type EventType<T> = "Event";
class EventEmitter {
on<A, E: EventType<A>>(name: E, listener:(event: A) => void): EventEmitter {
// ...
return this;
}
}
var type: EventType<Event> = "Event";
var e = new EventEmitter();
e.on(type, (event) => {
console.log(event.type);
console.log(event.hoge); // Error!!
});
できました。
Flowtypeでは
type Hoge = "Hoge";
var str1: Hoge = "Hoge";
var str2: Hoge = "Fuga"; // Error!!
という感じに文字列リテラルを制限する型(何て言うんだろ?)を作れるので、これにPhantom Typeで型を付与してコールバックの引数に渡す型を定義できるようにします。
type EventType<T> = "Event";
これにon
で受け取って、コールバックの引数にEventType<T>
のT
を渡せるように定義します。
on<A, E: EventType<A>>(name: E, listener:(event: A) => void): EventEmitter
これでイベント名に対応するコールバックの引数を定義できるようになります。Phantom Type最高ですね!!!
と、言いたいところですが、このコードはFlowtypeで想定通りに動きません・・・。
どうやらtype
を使ってPhantom Typeを行おうとすると型がany
に落とされてしまうようで、
console.log(event.hoge); // Error!!
でエラーが発生してくれないようです。
実際にエラーを出すには
type Event = { type: string; };
type EventType<T> = "Event";
class EventEmitter {
on<A, E: EventType<A>>(name: E, listener:(event: A) => void): EventEmitter {
// ...
return this;
}
}
var type: EventType<Event> = "Event";
var e = new EventEmitter();
e.on(type, (event: Event) => {
console.log(event.type);
console.log(event.hoge); // Error!!
});
とコールバックの引数に型を指定してやる必要があります。
何というかPhantom Typeを使う意味が全くありませんね!!!!
class
を使えばany
にならずに想定した通りに動くので、その形に変更します。
JSでの実用例(現実)
type Event = { type: string; };
class EventType<T> { };
class EventEmitter {
on<A, E: EventType<A>>(name: Class<E>, listener:(event: A) => void): EventEmitter {
// ...
return this;
}
}
var type: Class<EventType<Event>> = EventType;
var e = new EventEmitter();
e.on(type, (event) => {
console.log(event.type);
console.log(event.hoge); // Error!!
});
できました。
イベント名にクラスを指定する形になったので、実際に実用するにはnodeのEventEmitter
などをラップしてEventType
を文字列に落とす仕組みが必要になってきます。
とはいえ、これでイベント名に対応するコールバックの引数を静的にチェックをすることができるようになりました。Phantom Type最高ですね!!!
おわりに
ひさしぶりにFlowtypeを触りましたが出来ることが本当に多くなりましたね。
今回は使用しませんでしたが、FlowtypeではコメントでFlowtype用のコードを書けるため、通常の静的型言語ではできない、型のためのコードと実際に動くコードを分けたりすることも出来たりします。
なかなか表現としておもしろいので皆さんもFlowtypeで楽しんでみてください。