flowtypeとOpaque Type Aliases

flowtypeの[v0.51.0](https://github.com/facebook/flow/blob/master/Changelog.md#0510)からOpaque Type Aliasesという機能が入りました。

[Opaque Type Aliases - Enforcing abstraction through the type system](https://flow.org/en/docs/types/opaque-types/)

これは`A`という型に`B`という別名をつけ、同一ファイルでは`A=B`として扱い、別ファイルでは`A≠B`として扱える機能です。
とても素敵な機能なのですが、あまり話題にならなくて寂しかったので、どうやって使うのか、どこで使うのかなんかを書こうと思います。

# Opaque Type Aliase

Opaque Type Aliaseは先ほども書いた通り、`A`という型に`B`という別名をつけ、同一ファイルでは`A=B`として扱い、別ファイルでは`A≠B`として扱える機能です。
これを実際にコードに落とすと下記のようになります。

```js:opaque.js
// @flow
type A = number;
opaque type B = A;

let b: B = 1; // Not Error

export type {B};
```

```js
// @flow
import type {B} from './opaque.js';

let b: B = 1; // Error
```

`A`は`number`型の別名で、`B`は`A`の別のため、`B=A=number`な形になります。
Opaque Type Aliaseは同一ファイルでは`A=B`と通常のType Aliasesと同じ挙動になるため、`let b: B = 1`と`B`に`number`型の値を代入することができます。

しかし、別ファイルでは`A≠B`という扱いになるため、`let b: B = 1`と`number`型の値を代入するとエラーになってしまいます。

これにより例えば同一ファイルでFactoryを提供すれば、Factoryを経由しないと生成できない型を提供することができるようになります。
（厳密に言えば`any`に型変換すれば生成することはできます。が、そうすると`flow coverage`でよろしくないコードとして検出されることになるので適切にCIを回していれば問題ないと思います）

# Super Type

先ほどの例ではOpaque Type Aliaseの基本的な使い方はわかりますが、どこで使えばいいのか良さがあまりわからないと思います。
Opaque Type Aliaseの真価を発揮するのはSuper Typeを組み合わせたところからになります。

```js
opaque type B: SuperType = A;
```

Opaque Type Aliaseでは上記のように`SuperType`を別名に付けることができます。
`SuperType`を付けると同一ファイルでは同じ挙動になりますが、別ファイルでは`A≠B`のまま`A`として振舞うことができます。

```js:opaque.js
// @flow
type A = number;
opaque type B = A;
opaque type C: A = A;

export type {B, C};
```

```js
// @flow
import type {B, C} from './opaque.js';

declare var b: B;
b.toFixed(); // Error

declare var c: C;
c.toFixed(); // Not Error
```

`B`では`SuperType`を指定していなため`A`（`number`型）として振舞うことはできませんが、`C`は`SuperType`に`A`を指定しているため`A`（`number`型）として振舞うことができています。
もちろん、Opaque Type Aliaseとして`A≠B`は維持されているため

```js
let c: C = 1;
```

とした場合はエラーになります。

このように`SuperType`をOpaque Type Aliasesで指定することによって、`A`として振る舞いたいが、`A`とは違う型として扱いたい型を作ることができるようになります。

それではどこで使うのか実例を何点か挙げてみます。

# 実例1: 文字列型のIDを他の文字列型と区別する

一つ目の実例はIDの特殊な型を作る例です。
例えば、文字列型のIDの型と名前や住所など他の文字列型と区別がつかないため誤代入が発生します。

そこでOpaque Type Aliasesを使い、文字列型として扱いつつ、他の文字列型と区別できるように別名を付けてあげます。

```js:id.js
// @flow
export opaque type ID: string = string;
export function id(s: string): ID {
  return (s: ID);
}
```

```js
// @flow
import type {ID} from './id.js';
import {id} from './id.js';

type Entity = {
  id: ID;
  name: string;
}

let entity: Entity = {
  id: id('abc'),
  name: 'foo'
};

entity.id = entity.name; // Error
```

従来であればクラスを作成して区別をする必要があったのが、別名をつけるだけでプリミティブな型を維持しつつ特別な型にできるというのはポイントが高いです。
特にJavaScriptではJSONのエンコード/デコードは頻発するので、クラスではなく、そのまま扱えるプリミティブな型のまま扱える恩恵ははかりしれません。

# 実例2: Entity毎のIDを区別する

二つ目の実例はEntity毎のIDを区別をする例です。
例えば`User`の`id`と`Product`の`id`は別物になります。
しかし、これをどちらも文字列型として扱った場合に、本来は違うIDが混在できてしまう問題が発生します。

```js:user.js
// @flow
export opaque type UserId: string = string;
export type User = {
  id: UserId;
  name: string;
};

export function user(id: string, name: string): User {
  return {
    id: id,
    name: name
  };
}
```

```js:product.js
// @flow
export opaque type ProductId: string = string;
export type Product = {
  id: ProductId;
  name: string;
};

export function product(id: string, name: string): Product {
  return {
    id: id,
    name: name
  };
}
```

```js
// @flow
import {user} from './user.js';
import {product} from './product.js';

var u = user('abc', 'foo');
var p = product('abc', 'foo');

u.id = p.id; // Error
```

そこで。上記のように`UserId`と`ProductId`と別々のOpaque Type Aliasesで別名を付与することで、同じ文字列型のIDでも別の型として取り扱うことができます。
もちろん、どちらのIDも文字列型として振舞うことができるため、例えばロギングのときなどに特別な変換処理を入れなくてもそのままIDを出力することができます。

# 実例3: 述語を付与する

三つ目は既存の型に述語を付与する例です。
[flowtypeで既存の型に述語を付与してassertを削除しよう](https://qiita.com/kinzal/items/d3258fa41a646556709a) で型に述語を付与する話を書きましたが、Opaque Type Aliaseを使用することでより簡易に書くことができます。

```js:nes.js
// @flow

export opaque type NonEmptyString: string = string;
export function nes(s: string): Promise<NonEmptyString> {
  return (s.length > 0)
    ? Promise.resolve(s)
    : Promise.reject(new Error('string is not empty.'))
}
```

```js
// @flow
import type {NonEmptyString} from './nes.js';
import {nes} from './nes.js';

function foo(str: NonEmptyString) {
  str.charAt(0);
}
async () => {
  foo(await nes('abc'));
};
```

これは先の記事と同じNon Empty Stringを実現する例ですが、こちらの方がより短くなっていることがわかるでしょうか。
今までは文字列であり、Non Empty Stringであるということを`any`を使って表現していたのが、Opaque Type Aliasesを使うことでより型安全に実現することができました。

# おまけ

先日、Opaque Type Aliasesの機能は良いんだけど、型を変換するだけの関数を何度も呼び出すのは嫌だよねーという話をしていたら良いものを教えてもらいました。

* [Prepack - A tool for making JavaScript code run faster.](https://github.com/facebook/prepack)

これなら関数のインライン展開してくれるし、flow+prepackというのがそのうち鉄板構成になるかもしれませんね。
（Opaque Type Aliasesに限らずflowtypeでゴリ押すために型のための関数はよく作るので）

そのうちflowtype+prepackの記事を書こうと思うので、今回は紹介まで。

# まとめ

最後に一点だけ注意です。
Opaque Type Aliaseを使えばどんな特殊な型を作ることもできますが、やはり工数的な意味でコスパの悪いところまで作りむのはオススメできません。
できるなら汎用的に扱えそうなところや、金額や単位系などセンシティブなところに適用していくと素敵なflowtypeのコードになると思います。

というわけで、Opaque Type Aliasesは素敵な機能なので、ぜひぜひ皆さんお使いください。