はじめに
TypeScriptの構造的部分型のトラップを明確にするため、無闇なスプレッド構文の使用が型システムの健全性を脅かし得ることを、万能型変換関数を作成することで示します。
本記事のコードは MIT License とします。
万能型変換関数
任意の型T, SについてT型の値をS型に変換します。
変換を実行するためには、S型の値の存在証明が必要なため証人を一つ与える必要があります。
const convert = <T, S>(value: T, witness: S): S => {
const obj1 = { a: witness, b: null } satisfies { a: S, b: null };
const obj2 = { a: value, b: null } satisfies { a: T, b: null };
const f = (x: { a: T, b: null }): { b: null } => x;
const obj = { ...obj1, ...f(obj2) } satisfies { a: S };
return obj.a;
};
使い方
文字列 "str" を number 型に変換するには次のようにします。
number 型の証人としては 0 を与えます。
const n = convert("str", 0) satisfies number;
console.log(n satisfies number); // => "str"
console.log(typeof (n satisfies number)); // => string
実行時には string が返りますが、TypeScript の型システム上は number として扱われます。
おわりに
TypeScriptの構造的部分型とスプレッド構文を組み合わせることによって、万能型変換関数が作れてしまうことを示しました。意図せずに実体と異なる型を付けてしまわないように気をつけてTypeScriptを使いましょう。