制御フロー分析で重要な判別可能なユニオン型
制御フロー分析の話を進めていくと、登場するのが判別可能なユニオン型という考え方です。
判別可能なユニオン型とは
前項のリスト5のReturnJSONの考え方をもう少し進めて、リスト7のようなReturnJSONSuccessとReturnJSONFailを定義してみます。これは、サーバ側の処理が成功したか失敗したかで、そのデータ型を別々のインターフェースとしたものです。
interface ReturnJSONSuccess {
status: 1;
result: string;
}
interface ReturnJSONFail {
status: 0;
msg: string;
}
ここで注目するのは、statusプロパティです。それぞれ、型として1と0が定義されています。これはいわゆるリテラル型であり、しかも、ReturnJSONSuccessでは1、ReturnJSONFailでは0以外の値が格納できないようになっています。このような単一の値の型定義のことを、リテラル型の中でも、特に「ユニット型(Unit Types)」といいます。
そして、ユニット型が定義された特定のプロパティを共通に持つオブジェクト型のユニオン型を「判別可能なユニオン型(Discriminated Unions)」といいます。例えば、リスト7のReturnJSONSuccessとReturnJSONFailのユニオン型であるReturnJSONを定義し、それを引数の型とする関数showResult()を考えるならば、リスト8のようなコードが成り立ちます。
type ReturnJSON = ReturnJSONSuccess | ReturnJSONFail;
function showResult(returnJSON: ReturnJSON) {
if(returnJSON.status == 0) { // (1)
console.log(returnJSON.msg); // (2)
}
else { // (3)
console.log(returnJSON.result); // (4)
}
}
リスト8では、(1)と(3)でstatusプロパティの値が0かそれ以外での制御フロー分析を行っています。ReturnJSONSuccess、ReturnJSONFail、および、そのユニオン型であるReturnJSONの型定義のおかげで、returnJSONには必ずstatusプロパティが存在し、しかも、先述のように、その値は、0か1のどちらかであるのが、型定義として保証されているからです。
さらに、その条件ブロック内では型の絞り込みが行われ、(2)の段階のreturnJSONはReturnJSONFail型、同様に(4)の段階のreturnJSONはReturnJSONSuccess型が保証され、それぞれのプロパティへのアクセスがエラーなく行えます。これが、判別可能なユニオン型の特徴です。
判別可能なユニオン型がユニット型以外でも可能に
この判別可能なユニオン型の判別を行うプロパティ(リスト8ならばstatus)は、ユニット型である必要がありました。これが、バージョン3.2でユニット型以外もサポートされるようになりました。これにより、例えば、リスト9のような型定義を行い、errorMsgがundefinedかどうか、また、notFoundMsgがundefinedかどうかで、リスト9と同じような制御フロー分析が可能となります。
interface ReturnJSONSuccess {
errorMsg: undefined;
notFoundMsg: undefined;
result: string;
}
interface ReturnJSONFail {
errorMsg: string;
notFoundMsg: undefined;
result: string;
}
interface ReturnJSONNotFound {
errorMsg: undefined;
notFoundMsg: string;
result: string;
}
type ReturnJSON = ReturnJSONSuccess | ReturnJSONFail | ReturnJSONNotFound;
判別可能ユニオン型の分割代入による制御フロー分析
この判別可能ユニオン型のオブジェクトを分割代入した各変数を用いた制御フロー分析が、バージョン4.6で可能となりました。これを用いると、リスト9のReturnJSON型を引数とする関数showResult()は、リスト10のような制御フロー分析が可能となります。
function showResult(returnJSON: ReturnJSON) {
const {errorMsg, notFoundMsg, result} = returnJSON;
if(errorMsg != undefined) {
:
}
:
}
タプルを利用した制御フロー分析
ここまで、判別可能なユニオン型のオブジェクトを利用した制御フロー分析を紹介してきました。この仕組みは、バージョン4.6でタプルでも可能となりました。例えば、リスト11のようなコードです。
type ReturnJSONSuccess = [1, string]; // (1)
type ReturnJSONFail = [0, string, string]; // (2)
type ReturnJSONNotFound = [404, string, string]; // (3)
type ReturnJSON = ReturnJSONSuccess | ReturnJSONFail | ReturnJSONNotFound; // (4)
function showResult(...returnJSON: ReturnJSON) { // (5)
if(returnJSON[0] == 0) { // (6)
const errorMsg = returnJSON[1];
:
}
:
}
リスト11の(1)〜(3)では、リスト9のReturnJSONSuccess、ReturnJSONFail、ReturnJSONNotFoundとほぼ同等の働きをするタプルを、同名で定義しています。その第1要素(インデックス0)が、各タプルの判別の元となる値となっています。そして、(4)でこれらのタプルのユニオン型としてReturnJSONを定義し、そのReturnJSONを引数の型とする関数showResult()を定義しています。ただし、その際、...演算子を付与し、残余引数としています。このタプルと残余引数の組み合わせに関しては、第2回を参照してください。
このような引数returnJSONに対して、(6)では、第1要素の値を判定しており、この判定でもって制御フロー分析と同等の効果が得られます。すなわち、(6)のブロック内では、図5のように、returnJSONはReturnJSONFail型として扱われ、型の絞り込みが行われているのがわかります。
