flowtypeのdeclareとimportの話

flowtypeの導入を断念しました。とはいえ数週間ほどやって知見が溜まったので、それをメモがてら残しておきます。

# 前提

前提としてFlow Commentsで使用しています。

* [Flow Comments](http://flowtype.org/blog/2015/02/20/Flow-Comments.html)

これは良いか悪いかさておき、実装としてのコードと、型を補完するコードを分けることができるので、融通が利くというのでFlow Commentsを使用しています。つまりはこういうことです。

```js
var awslambda/*: Lambda */ = new AWS.Lambda();
var lambda = Bluebird.promisifyAll(lambda, {suffix: 'Promise'});
/*::
lambda = {};
lambda.addPermissionPromise = Bluebird.promisify(awslambda.addPermission);
lambda.createEventSourceMappingPromise = Bluebird.promisify(awslambda.createEventSourceMapping);
lambda.createFunctionPromise = Bluebird.promisify(awslambda.createFunction);
lambda.deleteEventSourceMappingPromise = Bluebird.promisify(awslambda.deleteEventSourceMapping);
lambda.deleteFunctionPromise = Bluebird.promisify(awslambda.deleteFunction);
lambda.getEventSourceMappingPromise = Bluebird.promisify(awslambda.getEventSourceMapping);
lambda.getFunctionPromise = Bluebird.promisify(awslambda.getFunction);
lambda.getFunctionConfigurationPromise = Bluebird.promisify(awslambda.getFunctionConfiguration);
lambda.getPolicyPromise = Bluebird.promisify(awslambda.getPolicy);
lambda.invokePromise = Bluebird.promisify(awslambda.invoke);
lambda.invokeAsyncPromise = Bluebird.promisify(awslambda.invokeAsync);
lambda.listEventSourceMappingsPromise = Bluebird.promisify(awslambda.listEventSourceMappings);
lambda.listFunctionsPromise = Bluebird.promisify(awslambda.listFunctions);
lambda.removePermissionPromise = Bluebird.promisify(awslambda.removePermission);
lambda.updateEventSourceMappingPromise = Bluebird.promisify(awslambda.updateEventSourceMapping);
lambda.updateFunctionCodePromise = Bluebird.promisify(awslambda.updateFunctionCode);
lambda.updateFunctionConfigurationPromise = Bluebird.promisify(awslambda.updateFunctionConfiguration);
*/
```

（ツラいですが！ツラいですが！！！


まぁ、型のためのコードをコメントとして残せるので、ツラいことに目をつぶればどうにでも出来るというのはメリットです。

# 外部宣言ファイルの読み込み

`declare`で行う宣言は外部ファイルで定義して、checkするときにそのファイルを読み込むということができます。

* [Flow | Checking third-party code](http://flowtype.org/docs/third-party.html)

外部宣言したファイルを読み込む方法として2つあり、`flow check　--lib [path]`で指定する方法と、`.flowconfig`で`[libs]`を定義する2つの方法があります。
どちらを使うかは好き好きかなと思います。

# declare

flowtypeではDeclarationsとして型を宣言する機能があります。これとImportを組み合わせることで3rdパーティのモジュールを型定義して読み込むことができます。

* [Flow | Declarations](http://flowtype.org/docs/declarations.html)
* [Flow | Import Types](http://flowtype.org/blog/2015/02/18/Import-Types.html)

## declare module

存在しないモジュールを読み込む構文として`declare module`があります。

http://flowtype.org/docs/declarations.html#declarations

> ```js
> declare class C {
>   x: string;
> }
> declare module M {
>   declare function foo(c: C): void;
> }

declare moduleで宣言したあとに

> ```js
> var M = require('M');
> M.foo(new C());
> ```

存在しないモジュールを`require`、または`import`を行うと`declare module`で宣言したモジュールを受けとることができます。
注意点として、`declare module`構文はモジュールを定義するための構文ではなく、`require`（または`import`）できないモジュールを定義するものになるということを気をつけてください。自分はモジュールを定義するための構文だと思ってハマりました。

使い所としては結構難しく、思いつくものとしては

* 開発中のモジュールのインターフェースを読み込む

ぐらいしか思いつきません。それ以外に使い道が全く思いつかないので、こういうときに使うというのがあれば教えてください。

# declare var, function, class

`declare module`以外の宣言の場合は

```js
import /*:: type */ Package from 'Package';
```

のように`import type`構文を使って、モジュールを読み込み、それに対してdeclareで宣言した型を付与します。（単純に型を読み込むだけなら外部宣言ファイルの読み込みを行えば使えるようになります）

ただし、[aws-sdk](https://github.com/aws/aws-sdk-js)のようにクラス内クラスはflowtypeでは宣言できない（たぶん）ため、少し手間をかける必要があります。

```js:interface/lambda.js
declare class Lambda {
  addPermission(request: AddPermissionRequest, callback: (err: any, response: AddPermissionResponse) => void): void;
  createEventSourceMapping(request: CreateEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
  createFunction(request: CreateFunctionRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
  deleteEventSourceMapping(request: DeleteEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
  deleteFunction(request: DeleteFunctionRequest, callback: (err: any, response: void) => void): void;
  getEventSourceMapping(request: GetEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
  getFunction(request: GetFunctionRequest, callback: (err: any, response: GetFunctionResponse) => void): void;
  getFunctionConfiguration(request: GetFunctionConfigurationRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
  getPolicy(request: GetPolicyRequest, callback: (err: any, response: GetPolicyResponse) => void): void;
  invoke(request: InvocationRequest, callback: (err: any, response: InvocationResponse) => void): void;
  invokeAsync(request: InvokeAsyncRequest, callback: (err: any, response: InvokeAsyncResponse) => void): void;
  listEventSourceMappings(request: ListEventSourceMappingsRequest, callback: (err: any, response: ListEventSourceMappingsResponse) => void): void;
  listFunctions(request: ListFunctionsRequest, callback: (err: any, response: ListFunctionsResponse) => void): void;
  removePermission(request: RemovePermissionRequest, callback: (err: any, response: void) => void): void;
  updateEventSourceMapping(request: UpdateEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
  updateFunctionCode(request: UpdateFunctionCodeRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
  updateFunctionConfiguration(request: UpdateFunctionConfigurationRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
}
```

上記のようにAWSオブジェクト内部のクラスを単独で宣言し

```js
import AWS from 'aws-sdk';
var lambda/*: Lambda */ = new AWS.Lambda();
```

AWSオブジェクトを通常のimportで読み込んだあとに変数に型定義を付与してあげます。
理想を言えば

```js
declare class AWS {
  static Lambda: Lambda;
}
```

みたいに宣言をできたら良かったのですが。。。
というか、もっと言えば、d.tsと同じ構文で宣言したかったです。


# おわりに

取り敢えず、たかだが宣言とインポートをやりたかっただけなのに、ここまで理解するのに一体何個の地雷を踏んだんだろうというぐらいツラい道のりでした。
ES6+flowtype構成とか実用するには、まだ早すぎました・・・。