12
14

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でPhantom Typeを楽しむ

Last updated at Posted at 2016-01-04

久しぶりに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で楽しんでみてください。

12
14
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
12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?