flowのinterfaceでハマる

flowのinterfaceは便利だが、2016/12 時点では、
気をつけないといけない不具合がある。

本記事で利用したflowのバージョンはv0.34.0。

# flowのinterface

JSの静的型チェッカであるflowは、interfaceも定義できる。

https://flowtype.org/docs/syntax.html#interface-declarations


> ## Interface declarations
Declaring an interface creates a type that multiple classes can satisfy without being part of the same inheritance hierarchy.
>
> Like type aliases, interfaces are entirely erased at compile time and have no runtime presence.


「インターフェイス宣言は、継承の階層関係なしに、複数のクラスが満たす型を生成します」と。


# 使う

何か、転がして、数値か文字列を得るという仕様を、
`Rollable`というインターフェイスとして定義。

```js
// @flow

interface Rollable {
  roll(): number | string;
}
```

サイコロ `Dice`クラスを下記のように定義。数値が返る。

```js

class Dice {
  roll(): number {
    return Math.floor(Math.random() * 6) + 1
  }
}
```

ごきげんようの、文字のサイコロを、下記のように定義。

```js
class Gokigenyo {
  roll(): string {
    const values = ['情けない話', '恋の話', '輝いてた話',
                    '怖い話', '笑える話', 'あたりめ']
    return values[Math.floor(Math.random() * 6)]
  }
}
```

そうすると、2つのクラスののインスタンスは、Rollable型とも解釈される。

```js
const rollable1: Rollable = new Dice()
const rollable2: Rollable = new Gokigenyo()
```

**戻り値の型が違くても、うまくまとめてくれた。抽象化できてうれしい**。


# プロパティ型は厳密一致を強いる

↑のコードを以下のように書き換えると、うまくいかない。

まず、「当たり」のように特別な値`specialValue`があるという仕様を、
`SpecialRollable`というインターフェイスとする。

```js
// @flow

interface SpecialRollable {
  specialValue: number | string;
  roll(): number | string;
}
```

サイコロは、6が特別な値とする。

```js

class Dice {
  specialValue: number;

  constructor() {
    this.specialValue = 6
  }

  roll(): number {
    return Math.floor(Math.random() * 6) + 1
  }
}
```

ごきげんよう、は「あたりめ」が特別な値とする。

```js

class Gokigenyo {

  specialValue: string;

  constructor() {
    this.specialValue = 'あたりめ'
  }

  roll(): string {
    const values = ['情けない話', '恋の話', '輝いてた話',
                    '怖い話', '笑える話', 'あたりめ']
    return values[Math.floor(Math.random() * 6)]
  }
}
```

で、下記のようにつかってみると...

```js
const rollable: SpecialRollable = new Dice()
```

```text
  4:   specialValue: number | string;
                              ^^^^^^ string. This type is incompatible with
  9:   specialValue: number;
                     ^^^^^^ number
```

型で怒られた。

このエラーは、**`specialValue`は`number | string`型で来て欲しいのに、`number`型で来てるよ**というもの。


戻り値の型は、包含関係にあれば許容していたが、プロパティの型は、厳密一致を強いるようになっている。


## 嬉しくない解決策1
インターフェイスに、型パラメータを導入して、利用するときに入れる。

```js
// @flow

interface SpecialRollable<T: number | string> {
  specialValue: T;
  roll(): T;
}
```

```js
const rollable1: SpecialRollable<number> = new Dice()
const rollable2: SpecialRollable<string> = new Gokigenyo()
```

利用側がクラスを意識しないといけないので、あまり嬉しくない。


## 嬉しくない解決策2
any型にすればなんでも許されるというもの。

```js
// @flow

interface SpecialRollable {
  specialValue: any;
  roll(): number | string;
}
```

`specialValue`が定義されていないクラスは許容しないので、**プロパティ名が存在する**という仕様は強いることができる。nullも許容するけど。



# オブジェクト定義も、プロパティは厳密一致を強いる
嬉しい解決策を提示しないどころか、もう一個ダメな例を紹介。

```js
const rollable: SpecialRollable = {
  specialValue: 1,
  roll() { return 1 }
}
```

クラスを介さず、直接オブジェクトを書いても、やはりプロパティの型が厳密一致でないため怒られる。

## これには解決策があった
インターフェイスを使わず、type aliasを使えば怒られなくなった。

```js
type SpecialRollableType = {
  specialValue: number | string;
  roll(): number | string;
}
```

```js
const rollable: SpecialRollableType = {
  specialValue: 1,
  roll() { return 1 }
}
```

関連issue [#1569](https://github.com/facebook/flow/issues/1569)を参照のこと。


# 関連issue
- https://github.com/facebook/flow/issues/1569
- https://github.com/facebook/flow/issues/2437

これらをみると、この挙動は意図されたものではなく、修正予定である。