こんにちは。最近TypeScriptの型レベルプログラミングにハマっています。
今回実装する「型推論やりすぎ足し算」をする関数はこちらです。
既出だったらごめんなさい。
(画像が読み込めない時用のコード)
// 実装省略
declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>;
const a = add(12, 34); // testの型が46になる
a
の型が 46
になり、型レベルでも足し算をして、その結果を返します。
Add<T, U>
という型は用意されていないので、これを工夫して作ります。
TypeScriptには、リテラル型というのがあり、 "hello"
や 10
、 true
などの値も型となりえます。
このようにして、型レベルでも足し算を行うのが、この「やりすぎ」関数となっています。
ちなみに、普通にジェネリクスを使って実装しても、返り値は number
となってしまいます。
function add2<T extends number, U extends number>(a: T, b: U) {
return a + b;
}
const b = add2(12, 34); // 型はnumber
この a + b
という演算が返す型はリテラル型ではなく number
にされてしまうのですね。
使用したTypeScriptのバージョン
TypeScript 4.2.3
型レベルプログラミングは、TypeScriptのバージョンの違いをもろに受けるので、気を付けてください。
足し算部分の実装
まず、型 Add<T, U>
の実装ですが、ここでは簡単に載せます。
type Repeat<T extends number, R extends any[] = []> = R["length"] extends T ? R : Repeat<T, [any, ...R]>;
type Add<T extends number, U extends number> = [...Repeat<T>, ...Repeat<U>]["length"];
Repeatで任意長(ただし再帰制限のため45程度まで)のTupleを作って、それを用いて足し算します。Tupleの長さが数値リテラル型で返ってくることから、それを足し算結果とします。
え、どうしてわざわざTupleを用いているの?などの背景の説明については、
ブログの記事で詳しく説明したので、そちらをご覧ください。
参考はいつもお世話になっているこのサイトのページです。
なお、再帰制限を回避してもっと大きな数まで扱いたい場合は、 Multiple<1, ...>
を用いる手があります。内部的に文字列操作を行っているのです。この実装はややこしく、ちょっとテーマとそれるので、先ほどの記事を参照してください。
関数部分の実装
後は、これを関数の定義に含めれば完成です。
declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>;
T,U
は実は引数に応じてリテラル型に推論してくれるみたいです。助かった。
実装を含める場合は、次のようにします。
a + b
は number
型となってしまうので、 as any as
が必要です。
function add<T extends number, U extends number>(a: T, b: U): Add<T, U> {
return a + b as any as Add<T, U>;
}
まとめ
型レベルプログラミングが、実際のプログラミングにも役に立ってくるともっと楽しいですね。
この関数の使いどころがあるかどうかはわかりませんがw
今回のプログラムは、こちらで試すことができます。