プログラムは、異なる種類のデータを同時に扱う必要がある場合があります。データの形状は、コードが見ているデータの種類によって異なる可能性があります。この種のプログラミングは、関数型プログラミング言語では非常に一般的であるため、ほとんどすべての関数型プログラミング言語には、次の方法が備わっています。
- 「タグ」で区別される、互いに素なケースのセットによってそのようなデータを指定します。各タグは、異なる「レコード」のプロパティに関連付けられています。(これらの記述は、「分離共用体」または「バリアント」型と呼ばれます。)
- タグをチェックし、関連付けられたプロパティのレコードに直接アクセスすることにより、そのようなデータに対してケース分析を実行します。(このようなケース分析を行う一般的な方法は、パターンマッチングです。)
このようなデータを分析または変換するプログラムの例としては、抽象構文木を扱うコンパイラから、例外値を返す可能性のある操作まで、さまざまなものがあります。
Flow 0.13.1以降、JavaScriptでこのスタイルのプログラミングをタイプセーフな方法で行うことができるようになりました。オブジェクト型の分離共用体を定義し、それらのオブジェクト型の一部の共通プロパティ(「センチネル」と呼ばれる)の値を切り替えることで、その型のオブジェクトに対してケース分析を実行できます。
分離共用体に対するFlowの構文は次のようになります。
type BinaryTree =
  { kind: "leaf", value: number } |
  { kind: "branch", left: BinaryTree, right: BinaryTree }
function sumLeaves(tree: BinaryTree): number {
  if (tree.kind === "leaf") {
    return tree.value;
  } else {
    return sumLeaves(tree.left) + sumLeaves(tree.right);
  }
}
問題
渡されたデータに応じて異なるオブジェクトを返す次の関数を考えてみましょう。
type Result = { status: string, errorCode?: number }
function getResult(op): Result {
  var statusCode = op();
  if (statusCode === 0) {
    return { status: 'done' };
  } else {
    return { status: 'error', errorCode: statusCode };
  }
}
結果は、'done'または'error'のいずれかであるstatusプロパティと、statusが'error'の場合に数値ステータスコードを保持するオプションのerrorCodeプロパティを含みます。
結果からエラーコードを取得する別の関数を記述してみましょう。
function getErrorCode(result: Result): number {
  switch (result.status) {
    case 'error':
      return result.errorCode;
    default:
      return 0;
  }
}
残念ながら、このコードは型チェックされません。Result型は、statusプロパティとerrorCodeプロパティの関係を正確に捉えていません。つまり、statusプロパティが'error'の場合、errorCodeプロパティが存在し、オブジェクトで定義されていることを捉えていません。その結果、Flowは、上記の関数でresult.errorCodeがnumberではなくundefinedを返す可能性があると判断します。
バージョン0.13.1より前はこの関係を表現する方法がなかったため、このシンプルで使い慣れたイディオムの型安全性をチェックすることができませんでした。
解決策
バージョン0.13.1以降、意図をより正確に捉え、動的な===チェックの結果に基づいてオブジェクトの可能な形状を絞り込むのに役立つ、より正確なResult型を記述することが可能になりました。これで、次のように記述できます。
type Result = Done | Error
type Done = { status: 'done' }
type Error = { status: 'error', errorCode: number }
つまり、結果の可能な形状を明示的にリストできます。これらのケースは、statusプロパティの値によって区別されます。ここでは、文字列リテラル型'done'と'error'を使用していることに注意してください。これらは、文字列'done'と'error'と正確に一致するため、これらの値に対する===チェックは、Flowが対応する型ケースを絞り込むのに十分です。この追加の推論により、関数getErrorCodeは、コードを変更することなく、型チェックされるようになりました。
文字列リテラルに加えて、Flowは数値リテラルもシングルトン型としてサポートしているため、分離共用体とケース分析でも使用できます。
開発の背景
分離共用体は、関数型プログラミング言語に広く普及している優れたプログラミングプラクティスの核心にあります。Flowで分離共用体をサポートすることで、JavaScriptはこれらのプラクティスをタイプセーフな方法で使用できます。たとえば、分離共用体を使用して、タイプセーフなFluxディスパッチャを記述できます。また、最近リリースされたGraphQLのリファレンス実装でも heavily 使用されています。