連載
» 2017年01月17日 05時00分 UPDATE

Dev Basics/Keyword:Flow(JavaScript用静的型チェッカー)

FlowはJavaScript用の静的型チェッカー。型推測機構や型注釈を利用することで、型安全なプログラミングを支援する。

[かわさきしんじ,Insider.NET編集部]
「Dev Basics/Keyword」のインデックス

連載目次

 FlowはJavaScript用の静的な型チェッカー。型推測機構や型注釈を利用することで、型安全なプログラミングを支援する。Facebookが開発し、オープンソースソフトウェアとして公開されている。

Flowの基本

 Flowは「データフロー」や「制御フロー」の解析を行うことで、JavaScriptで書かれたプログラムの意味を取り出し、その情報を基にコード中で使われている変数などの型を推測する。これにより静的な型チェックを行い、プログラムの実行前に型に関わるさまざまなエラーを検出してくれるものだ。また、必要があれば、型注釈をプログラムコードに付加することで、Flowによる型チェックが円滑に行われるようにもできる。

 以下に簡単な例を示す。なお、以下ではコードのキャプションをクリックすると、Flowのtryページにコードが展開されるようになっている(次の「2つのJavaScriptコードで構成されるプログラム」というサンプルを除く。また、Chrome以外のブラウザでは正しく動作しなかったり、処理が重くなったりするかもしれないので、クリックするときには注意されたい)。

// @flow
function foo() {
  return null;
}

var a = foo();
console.log(a.prop);


 上のコードは見た目には正しいJavaScriptプログラムだが、実行するとエラーが出るのは自明だ(関数fooがnullを返し、console.log呼び出しではnullに対してプロパティpropをアクセスしようとしている)。このコードをFlowでチェックすると、次のようにエラーを検出してくれる(実行環境については後述するが、ここでは「npm install -g flow-bin」コマンドでローカル環境にFlowをインストールしている)。

>flow
main.js:7
  7: console.log(a.prop);
                   ^^^^ property `prop`. Property cannot be accessed on possibly
null value
  7: console.log(a.prop);
                 ^ null


Found 1 error


変数aの値がnullであることを解析し、エラーを検出

 このサンプルは見た目にも分かりやすいものなので、効用が分かりにくいかもしれないが、静的にデータフロー/制御フローを解析することで事前に型エラーの発生を検出できるのがFlowの大きな特徴の1つといえる(なお、制御フローの解析を利用した型推測機能は現在ではTypeScriptにも取り入れられるようになっている)。

 また、上の例では型注釈が含まれていないことにも注目されたい。多くの場面では、Flowを利用する上で(プログラムコードの側に)必要となるのは1行目にあった「// @flow」というコメントだけだ。デフォルトでは、Flowは実行時にこのコメントが含まれているファイルだけを型チェックの対象とする。このようにすることで、既存の大規模プロジェクトやサードパーティー製のフレームワークを多数利用している場合でも、「まずは新規のJavaScriptコードだけをFlowによる型チェックの対象として、既存のコードは徐々にFlowを利用してチェックを行うようにする」ことが可能となっている。

 冒頭に述べたように、型注釈を必要に応じてコードに含めることも可能だ。特に複数のモジュール(ファイル)で構成される場合はモジュール境界で型注釈が必要となる。例えば、以下に示すような2つのファイルでプログラムが構成されているとしよう。

// module1.js
// @flow
function hello(name) {
  return "hello " + name;
}

module.exports.hello = hello;

// main.js
// @flow
var mod1 = require('./module1');

var v = mod1.hello('world');
console.log(v);

2つのJavaScriptコードで構成されるプログラム

 上のmodule1.jsファイルでは関数helloを定義/エクスポートしている。下のmain.jsファイルではこれを利用している。この段階ではhelloのパラメーターnameには型注釈がないことに注意しよう。そして、これをFlowでチェックすると、次のようなエラーが発生する。

>flow
module1.js:3
  3: function hello(name) {
                    ^^^^ parameter `name`. Missing annotation


Found 1 error


「型注釈がない」というエラー

 module1.jsファイルを次のように変更することで、このエラーは発生しなくなる。

// module1.js
// @flow
function hello(name: string) {
  return "hello " + name;
}

module.exports.hello = hello;

修正後のmodule1.jsファイル

 モジュール境界を越えて使用されるものについては、このように型注釈が必要となる。それでも多くの場合、型注釈は必要にはならないとFlow開発チームは考えているようだ。これは2014年11月にFlowの初期バージョンがリリースされた際のブログ記事(英語)に「Flowの基本設計においては、大抵のJavaScriptコードは暗黙的に静的な型付けが行われていることを前提としている」「コードが正しいかを判断するために開発者の頭の中では型が使われている」「コードのほとんどは暗黙的に静的型付けされているので、開発者が明示的に型注釈を行わずとも型エラーのチェックを行える」といった記述があることからも分かる(いずれも意訳)。

 例えば、以下のようなコードは型注釈を必要とすることなく、(論理的な)型エラーがあることが検出できる。関数add自体は数値の加算、文字列連結など、何らかの目的で書かれたものであろうが、それを使う側でブール値と文字列値という想定外の実引数を渡している。そのため、以下のコードはエラーとなる。

// main.js
// @flow
function add(a, b) {
  return a + b;  // エラー: ブール値と文字列値の加算となる
}

console.log(add(true, "1"));


 エラーを解消するにはパラメーターa、bに型注釈を付けるか、そもそもの関数add呼び出しを修正するなどが考えられる(前者の場合は「a: any, b: any」が考えられる。が、Flowにおけるany型は「この部分は型安全じゃないことをプログラマーが承知してコードを書いているよ」ということを意味するので多用は厳禁だ。ちなみに「a: boolean, b: string」と型注釈を書いても、Flowではブール値と文字列値の加算をエラーと判断するので意味はない)。

 なお、型注釈は2017年1月時点ではECMAScriptの仕様には含まれていない。ということは、型注釈を付加したコードはJavaScriptとしては正しいものではなくなる。Flowの立場はあくまでも「静的型チェッカー」であり、TypeScriptのようなトランスパイラとは異なるので、上記のコードを実行できるようにするためには、flow-remove-typesパッケージやBabelのプラグインなどを利用して、型注釈を取り除く必要がある。以下ではこれについて見ていこう。

Flowの実行環境

 一番お手軽にFlowを試してみるには、Flowの公式サイト内にある「try」ページがよい(ただし、冒頭でも述べたように、筆者が試したところではChrome以外のブラウザではうまく動作しなかったので注意が必要だ)。

Flowをオンラインで試しているところ Flowをオンラインで試しているところ

 実際にプロジェクトに組み込むにはnpmを利用して以下をインストールする。

  • flow-bin: Flowのコマンドラインインタフェース
  • flow-remove-types: 型注釈を取り除くユーティリティー

 これは「npm install -g 〜」コマンドでインストールするか、プロジェクトに組み込む場合には「npm install --save-dev 〜」コマンドでインストールしておく。これまでに見てきた例ではグローバルにインストールして、コマンドラインで「flow」コマンドを実行して、型チェックを行っていた。

 例として先ほどのコードに型注釈を付加した以下のコードを見てみよう(main.jsファイル)。

// main.js
// @flow
function add(a: any, b: any) {
  return a + b;
}

console.log(add(true, "1"));


 型注釈を取り除くには以下のコマンドを実行する。何もオプションを指定しなければ、コンソールに変換後のコードが表示される。

> flow-remove-types --out-file newmain.js main.js
> flow-remove-types --out-dir out main.js
> flow-remove-types . --out-dir out


main.jsファイルから型注釈を取り除くコマンド

 最初のものは出力ファイルを指定するもの。次のコマンドでは出力ディレクトリを指定している。最後のコマンドではカレントディレクトリ以下にあるファイルから型注釈を取り除いてoutディレクトリに保存している(ここではサンプルとしてカレントディレクトリを指定したが、これだと以下の画像のようにoutディレクトリにあるファイルまで処理されて、outディレクトリの下にoutディレクトリが作成されてしまう。これについてはサンプルということでご容赦願いたい)。なお、何もオプションを指定しなければ、コンソールに変換後のコードが表示される。

 実行結果を以下に示す。

実行結果 実行結果
最後の実行例を見ると、ファイルの配置には気を使う必要があることが分かる。

 あるいはflow-nodeコマンドを実行すれば、処理後のコードをNode.jsでそのまま実行してくれる。

> flow-node main.js
true1


flow-nodeコマンドの実行

 Babelを用いてFlowの型注釈を取り除くには、Babelのコマンドラインインタフェース(babel-cli)と「babel-plugin-transform-flow-strip-types」トランスフォーム(プラグイン)をインストールして、.babelrcファイルでの設定などを行っておく(詳細については公式サイト内の「Running Flow code」ページなどを参照されたい)。babel-plugin-transform-flow-strip-typesトランスフォームを使えるように設定したら、babelコマンドあるいはbabel-nodeコマンドを実行すればよい。なお、公式サイトでのおすすめはBabelを使った方法であり、flow-remove-typesパッケージは簡易的な位置付けとなっている。


 FlowはJavaScript用の静的型チェッカーであり、型推測や型注釈をサポートしている。本稿ではその基本となる部分だけを取り上げたが、Flowには独自の型システムが定義されている。JavaScriptのプリミティブ型(boolean、numberなど)、any型などシンプルな型だけでなく、インタフェース、TypeScriptの型ガードに相当する動的型テスト(Dynamic Type Tests)、配列、タプル、ジェネリクス型など、多くの型がある。これらを使うことで、JavaScriptプログラミングをより安全に行えるはずだ。また、FlowとTypeScriptとの違いについては以下の参考資料に挙げたURLなどを参考にしてほしい。

参考資料


「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。