https://zenn.dev/qnighy/articles/462baa685c80e2
TypeScriptではデザインパターンとしてtagged unionによる直和がよく使われます。このときパターンマッチに相当する処理はswitchで行われますが、そこで直和に対する分岐が網羅的であることの保証を実行時と型検査時の両方で賢く行う方法がこれまでも模索されてきました。
今回、ヘルパー関数を導入せずにいくつかの問題を同時に解決する賢い方法を思い付いたので共有します。
これだけです。
// switch (action.type) { ... default:
throw new Error(`Unknown type: ${(action as { type: "__invalid__" }).type}`);
// .. }
以下、より詳しく説明します。
TypeScriptではオブジェクトに type
プロパティーを用意し、決まった文字列を入れることで直和を実現するというデザインパターンが広く使われています。このとき、パターンマッチに相当する処理はswitchで行われます。
// GetまたはPutのどちらかをあらわす型
type Action = GetAction | PutAction;
type GetAction = {
type: "Get";
name: string;
};
type PutAction = {
type: "Put";
name: string;
value: string;
};
function act(action: Action) {
// Getの場合とPutの場合で場合分けする
switch (action.type) {
case "Get":
console.log(`get(${action.name})`);
break;
case "Put":
console.log(`put(${action.name}, ${action.value})`);
break;
}
}
しかし、このようにswitchを使った場合、以下のような問題があります。
Action
を更新してアクションの種別を増やしたときに分岐漏れが発生するが、そのことに型検査時・実行時に気付けない。そこで、型検査時と実行時の両方で、分岐の網羅性をチェックする方法がこれまで模索されてきました。
素朴な方法としては以下のようなコードが考えられます。
// Getの場合とPutの場合で場合分けする
switch (action.type) {
case "Get": /* ... */
case "Put": /* ... */
default:
throw new Error(`Unknown type: ${action.type}`);
}
これには次のような問題があります。