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

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

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

![](https://lh3.googleusercontent.com/pw/ACtC-3d0k0OE2gZtITyfmpsisK-MwM_Uw4p-_8I9DTOM35dhAy2kEOlOF2TR7W4cFwXkQUjonRzj8F-HnFPdD2Y5UH8XZBJG_hhD_sHEQfLRa_Brq2Onf-tRBW-A0op2kkAH13rlfFrKS6baOIR_Eq2IHsLXXA=w700-h104-no?authuser=0)

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

```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には、[リテラル型](https://typescript-jp.gitbook.io/deep-dive/type-system/literal-types)というのがあり、 `"hello"` や `10` 、 `true` などの値も型となりえます。
このようにして、型レベルでも足し算を行うのが、この「やりすぎ」関数となっています。

ちなみに、普通にジェネリクスを使って実装しても、返り値は `number` となってしまいます。

```typescript
function add2<T extends number, U extends number>(a: T, b: U) {
  return a + b;
}
const b = add2(12, 34); // 型はnumber
```

![](https://lh3.googleusercontent.com/pw/ACtC-3f5bBi62qp03aqewbWTFOdu3SF97nrUiuAkHQKtSInbfCE7PW0hSzsCWpsDibdZzW1RgxqgtYY14u7o4tFogd4_aYF5qBTGjSjDEl5jxPK1fKUGiGM85kYQSiPf-3Suu-sl5RC0kWcU2p6KAeXoGxaGwA=w700-h104-no?authuser=0)

この `a + b` という演算が返す型はリテラル型ではなく `number` にされてしまうのですね。

## 使用したTypeScriptのバージョン

**TypeScript 4.2.3**
型レベルプログラミングは、TypeScriptのバージョンの違いをもろに受けるので、気を付けてください。

## 足し算部分の実装

まず、型 `Add<T, U>` の実装ですが、ここでは簡単に載せます。

```typescript
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, ...>` を用いる手があります。内部的に文字列操作を行っているのです。この実装はややこしく、ちょっとテーマとそれるので、[先ほどの記事](https://asterisks.netlify.app/2021/05/01/ts-tuple-calc/#おまけ：Multipleを用いて、より大きい数を作る)を参照してください。

## 関数部分の実装

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

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

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

これで、冒頭の関数が実装できました。
![](https://lh3.googleusercontent.com/pw/ACtC-3d0k0OE2gZtITyfmpsisK-MwM_Uw4p-_8I9DTOM35dhAy2kEOlOF2TR7W4cFwXkQUjonRzj8F-HnFPdD2Y5UH8XZBJG_hhD_sHEQfLRa_Brq2Onf-tRBW-A0op2kkAH13rlfFrKS6baOIR_Eq2IHsLXXA=w700-h104-no?authuser=0)

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

`a + b` は `number` 型となってしまうので、 `as any as` が必要です。

```typescript
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

今回のプログラムは、[こちら](https://www.typescriptlang.org/play?ts=4.2.3#code/C4TwDgpgBAShkENgB4AqUIA9gQHYBMBnKXAVwFsAjCAJwBpYNs8ioFcQBtAXSgF4oPAHz9YnAEQAbPAHNgAC3G8sOAsXQB+RgC5Y8CEjQNO7EAwB0lmNyEBuAFChIUAIL58aJqtZkqtBgCqXizEvtQ0IgKcluZwiCioQhZW+oYBQtwS0rhyitwOAGakuADGwACWAPa4bO6eKiEkFOGBwWpNfhEAFAi6qAyUugEAlLpuHv1Q6VAA3vZQC1A0EMCkNDUIUADUUJRsxKb7rnWT6Q4AvvYl1YTAbKII7l0AjABMDADMACzDDte4hEq0nMkkqMh6v3sQA)で試すことができます。