型ガード関数 (type guard function)
TypeScriptのコンパイラはif
やswitch
といった制御フローの各場所での変数の型を分析しており、この機能を制御フロー分析(control flow analysis)と呼びます。
制御フロー分析の活用として、if
やswitch
といった制御構造で型ガードを使用することによって各場所での変数を特定の型に絞り込むことができます。
TypeScriptに元々用意されている型ガードとしてはtypeof
やinstanceof
がありますが、これ以外にもユーザーが独自に型ガードを定義することができます。
ユーザー定義の型ガード関数
ユーザー定義の型ガード関数を作るためには型述語(type predicate)と呼ばれる特殊な注釈を使用します。型述語の注釈は戻り値がboolean型の関数に対して適用でき、戻り値の型の注釈部分を次のように記述します。
ts
functionisDuck (animal :Animal ):animal isDuck {returnanimal instanceofDuck ;}
ts
functionisDuck (animal :Animal ):animal isDuck {returnanimal instanceofDuck ;}
animal is Duck
の部分が型述語です。これで関数isDuck()
がtrue
を返す時のif
のブロックの中ではanimal
はDuck
型として解釈されるようになります。
ts
// ここではquacks()は存在しないProperty 'quacks' does not exist on type 'Animal'.2339Property 'quacks' does not exist on type 'Animal'.animal .(); quacks if (isDuck (animal )) {animal .quacks ();// ...}
ts
// ここではquacks()は存在しないProperty 'quacks' does not exist on type 'Animal'.2339Property 'quacks' does not exist on type 'Animal'.animal .(); quacks if (isDuck (animal )) {animal .quacks ();// ...}
しかしながら、これはあくまでもその型であるとTypeScriptに解釈させるだけなので、JavaScriptとして正しいということは断言できません。
ts
functionisUndefined (value : unknown):value is undefined {return typeofvalue === "number";}
ts
functionisUndefined (value : unknown):value is undefined {return typeofvalue === "number";}
上記関数isUndefined()
は明らかに誤っていますが、この誤りに対してTypeScriptは何も警告を出しません。
型述語
型ガード関数の説明では、いきなり型述語という用語を使って説明しましたが、もう少し詳しく見てみましょう。
型述語という言葉を分解してみると「型+述語」となります。つまり型についての述語です。この述語という用語は元々は論理学に由来するものであり、その意味を知ることで型ガード関数についての理解を深めることができます。
たとえば、animal is Duck
という型述語は型ガード関数isDuck
の戻り値の注釈として使われていましたが、関数本体の実体を見ると単にboolean型の値を返す関数となっています。
ts
functionisDuck (animal :Animal ):animal isDuck {// ^^^^^^^^^^^^^^: 型述語returnanimal instanceofDuck ; // 単に真偽値を返す}
ts
functionisDuck (animal :Animal ):animal isDuck {// ^^^^^^^^^^^^^^: 型述語returnanimal instanceofDuck ; // 単に真偽値を返す}
元々、述語(predicate)とは、論理学において対象が持つ属性や関係などを表現するものです。たとえば、「Xは素数である(X is a prime number)」という命題Pがあったとき、Xを変数としてP(x)のように述語を表現できます。この述語P(X)は変数Xが素数の3などであれば真を返し、非素数の4などであれば偽を返します。これはまさに真か偽(真理値)を返す関数です。
このように述語とは変数を含んだ命題(=真理値を持つ判断)のことです。型述語(型についての述語)とはそのまま「型を変数に取る命題」ということができます。x is number
のような型述語は「xはnumber型である」という変数xが持つ型についての判断を表現しています。
ts
function isNumber(x: unknown): x is number {return typeof x === "number";}
ts
function isNumber(x: unknown): x is number {return typeof x === "number";}
先ほどの例で言えば、isDuck
関数は命題animal is Duck
について変数animal
を受けて真理値を返す関数となっています。
型ガード関数の説明で見たように、型注釈に型述語を用いることは単にboolean
型を返す関数であると型注釈するのとは異なる効果があり、制御フロー分析で型の絞り込みを行うためには型述語を利用する必要がありました。
ts
// 型述語が注釈されているので型ガード関数として機能するfunctiontypeGuard (x : unknown):x is number {return typeofx === "number";}// 単にboolean型の値を返す関数で型ガード関数として機能しないfunctionnotTypeGuard (x : unknown): boolean {return typeofx === "number";}declare constinput : number | string;// 型の絞り込みができるif (typeGuard (input )) {input ;} else {input ;}// 型の絞り込みができないif (notTypeGuard (input )) {input ;} else {input ;}
ts
// 型述語が注釈されているので型ガード関数として機能するfunctiontypeGuard (x : unknown):x is number {return typeofx === "number";}// 単にboolean型の値を返す関数で型ガード関数として機能しないfunctionnotTypeGuard (x : unknown): boolean {return typeofx === "number";}declare constinput : number | string;// 型の絞り込みができるif (typeGuard (input )) {input ;} else {input ;}// 型の絞り込みができないif (notTypeGuard (input )) {input ;} else {input ;}
このように型述語を持つ型ガード関数は制御フローにおいて静的に型の絞り込みを行うことができますが、単にboolean
型を返すという注釈がなされた関数ではそのように型の絞り込みができません。単に戻り値がboolean
型であると注釈してしまうと型ガードとして機能しなくなってしまうことに注意してください。
ただし、TypeScript 5.5からは型述語の注釈無しの次のような関数でも型ガード関数として機能するようになりました。関数の実体から型述語の型推論が可能となっているので、x is number
という型述語が推論されて型ガード関数となります。
ts
// 返り値の注釈がないこの関数は x is number という型述語で推論されるfunctionnoAnnotation (x : unknown) {return typeofx === "number";}
ts
// 返り値の注釈がないこの関数は x is number という型述語で推論されるfunctionnoAnnotation (x : unknown) {return typeofx === "number";}
5.5以降であっても、単にboolean型を返す関数として型注釈した場合には型ガード関数としては利用できないことに注意していください。
この型述語の機能強化によって配列のfilter
メソッドなどで使用するコールバック関数の型述語の記述なしで正確に型を推論できるようになるなどの改善が得られます。
関連情報
📄️ 制御フロー分析と型ガードによる型の絞り込み
TypeScriptは制御フローと型ガードにより、処理の流れに応じて変数の型を絞り込むことができます。