🦔

TypeScriptで型推論やりすぎな足し算をする関数を作ってみた

2021/05/06に公開

こんにちは。最近TypeScriptの型レベルプログラミングにハマっています。

今回実装する「型推論やりすぎ足し算」をする関数はこちらです。
既出だったらごめんなさい。

(画像が読み込めない時用のコード)

// 実装省略
declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>;
const a = add(12, 34); // testの型が46になる

a46 になり、型レベルでも足し算をして、その結果を返します
Add<T, U> という型は用意されていないので、これを工夫して作ります。

TypeScriptには、リテラル型というのがあり、 "hello"10true などの値も型となりえます。
このようにして、型レベルでも足し算を行うのが、この「やりすぎ」関数となっています。

ちなみに、普通にジェネリクスを使って実装しても、返り値は 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を用いているの?などの背景の説明については、
ブログの記事で詳しく説明したので、そちらをご覧ください。

https://asterisks.netlify.app/2021/05/01/ts-tuple-calc/

参考はいつもお世話になっているこのサイトのページです。

https://kgtkr.net/blog/2020/09/02/typescript-disable-recursion-limit

なお、再帰制限を回避してもっと大きな数まで扱いたい場合は、 Multiple<1, ...> を用いる手があります。内部的に文字列操作を行っているのです。この実装はややこしく、ちょっとテーマとそれるので、先ほどの記事を参照してください。

関数部分の実装

後は、これを関数の定義に含めれば完成です。

declare function add<T extends number, U extends number>(a: T, b: U): Add<T, U>;

T,Uは実は引数に応じてリテラル型に推論してくれるみたいです。助かった。

これで、冒頭の関数が実装できました。

実装を含める場合は、次のようにします。

a + bnumber 型となってしまうので、 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

今回のプログラムは、こちらで試すことができます。

Discussion