CoffeeScriptでも型が使いたい

null安全では無い言語はプログラミング言語では無いとまで言われる昨今。null安全以前の動的型付け言語にとっては不遇の時代と言っても差し支えが無い。CoffeeScriptも変換先であるJavaScriptに引っ張られ動的型付け言語である。しかし、それは型情報ができない、型チェックができないことを意味しているわけでは無い。Pythonのmypyのように型アノテーションを追加したコードに別途型チェックができれば良いのだ。そう、それが、CoffeeScript v2からの[Flow](https://flow.org/)対応である。

## 準備

まずは必要なパッケージをインストールし、環境を整える必要がある。なお`yarn`を用いているが、`npm`でも同様のことは可能だ。

```
yarn add -D coffeescript
yarn add -D flow-bin
```

Flowの準備をする。

```
yarn run flow init
yarn run flow status
```

コマンドが正常に完了すればOKだ。

## コード

早速コードを書いてみよう。

```sample.coffee
# @flow

###::
type Person = {
  name: string,
  age: number,
};
###

showAge = (person ###: Person ###) ###: string ### ->
  "#{person.name}は#{person.age}才です。"

taro ###: Person ### = {name: '太郎', age: 18}
console.log(showAge(taro))
```

`# @flow`がFlowのコードを表しており、必ず必要だ。`###: ###`は`/*: */`に、`###:: ###`は`/*:: */`に変換されるようになっている。これは[Comment Types](https://flow.org/en/docs/types/comments/)という書き方に準ずるようになっている。

## 実行とコンパイルとチェック

アノテーションはコメントであるためそのまま無視して実行する事も可能だ。

```
yarn run coffee sample.coffee
```

型チェックを行う場合は一旦JavaScriptに変換後に行う必要がある。

```
yarn run coffee --bare --no-header --compile sample.coffee
yarn run flow
```

`No errors!`と表示されていれば型が正しいと言うことになる。

JavaScriptに変換後は通常のFlowで書いた場合と同じである。違いはComment Typesを用いていることに過ぎない。Babelのflowプリセットを使えばコメント部分を削除することも可能だ。

## エラーになるパターン

実際にエラーになる所を見ないと本当にチェックしているかわからない。そこで、エラーになるようなコードも試して見よう。

```sample_error.coffee
# @flow

###::
type Person = {
  name: string,
  age: number,
};
###

showAge = (person ###: Person ###) ###: string ### ->
  "#{person.name}は#{person.age}才です。"

taro ###: Person ### = {name: '太郎', age: 18}
hanako ###: Person ### = {name: '花子'}
console.log(showAge(taro))
console.log(showAge(hanako))
console.log(showAge("三四郎"))
```

花子と三四郎を新たに追加しようとしたが、花子は年齢不詳、三四郎にいたっては直接文字列を渡してしまっている。そのまま実行してもエラーにはならないが、無意味な出力結果を出す事になるだろう。そこで、Flowによるチェックを行うと、おかしな部分を確認できる。実行結果は下記のようになる。

```
Error: sample_error.js:19
              v
 19: hanako = {
 20:   name: '花子'
 21: };
     ^ object literal. This type is incompatible with
  8: var hanako/*: Person */, showAge, taro/*: Person */;
                   ^^^^^^ object type
  Property `age` is incompatible:
      8: var hanako/*: Person */, showAge, taro/*: Person */;
                       ^^^^^^ property `age`. Property not found in
                  v
     19: hanako = {
     20:   name: '花子'
     21: };
         ^ object literal

Error: sample_error.js:25
 25: console.log(showAge(hanako));
                         ^^^^^^ object literal. This type is incompatible with the expected param type of
 10: showAge = function(person/*: Person */)/*: string */ {
                                  ^^^^^^ object type
  Property `age` is incompatible:
     10: showAge = function(person/*: Person */)/*: string */ {
                                      ^^^^^^ property `age`. Property not found in
     25: console.log(showAge(hanako));
                             ^^^^^^ object literal

Error: sample_error.js:27
 27: console.log(showAge("三四郎"));
                         ^^^^^ string. This type is incompatible with the expected param type of
 10: showAge = function(person/*: Person */)/*: string */ {
                                  ^^^^^^ object type


Found 3 errors
```

最初のエラーは`hanako`が`Person`型と一致しないことだ。そのため、`hanako`は`Person`型ではないと判断され、次のエラーも発生する。最後は、`Person`型では無く文字列を渡していることがエラーである。このように、Flowを使えばどこがどのような問題なのかを表示してくれる。対象はJavaScript変換後のコードになるが、CoffeeScriptは対応付けがわかりやすいJavaScriptに変換してくれるので、それほど苦労せずに問題箇所を把握できるだろう。

## まとめ

まずは導入とどんな感じであるかという紹介であるが、CoffeeScriptがさらに強力になるところがわかって貰えたと思う。ここらから、BabelでES5にトランスパイルしたり、webpackでまとめたりと一工夫必要になるのだが、そこら辺はおいおい手順を探っていきたいと思う。

こうして、型を手に入れたCoffeeScriptにもはや死角は無い。TypeScriptを打倒し、かつての栄光を取り戻す日も近い…かもしれない。