TypeScriptの再帰型定義で遊んでみた 

# はじめに
TypeScript の型定義の自由さはハマる面白さがありますよね。

TypeScript のリテラル型と Union Types で `1 | 2 | 3...` と書けるのを初めて見たとき
型バリデーションができる！？
とワクワクしたのですが、0～100 の範囲ともなるとさすがに書いてられません。
`NumberInRange<0, 100>` なんて書けたらな～、と思っていました。

ただ TypeScript では再帰型定義が難しく作るのは諦めかけていたのですが
こちらの記事で再帰型定義ができることを知り、なんとか作れました！圧倒的感謝。

[TypeScript で再帰的な型を定義する](https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2)
[TypeScriptで最低n個の要素を持った配列の型を宣言する方法](https://qiita.com/uhyo/items/80ce7c00f413c1d1be56)

その途中で遊んでて出来た型と一緒に紹介しておきます。

# n個の要素を持つ配列の型
紹介した記事で最低n個以上があったので、n個ピッタリも欲しいということで作ってみました。
再帰中では作るタプル自体をカウントにできるので、n個以上よりも簡単になりますね。

```typescript

type Append<Elm, T extends unknown[]> = ((
  arg: Elm,
  ...rest: T
) => void) extends ((...args: infer R) => void)
  ? R
  : never

type TupleNRec<Num, Elm, T extends unknown[]> = {
  0: T
  1: TupleNRec<Num, Elm, Append<Elm, T>>
}[T['length'] extends Num ? 0 : 1]

// n個の要素を持つタプル型
type TupleN<N extends number, T> = TupleNRec<N, T, []>
```

### 使用例
```typescript
// TupleN<3, number> -> [number, number, number]
const t1: TupleN<3, number> = [0, 1]         // Error!
const t2: TupleN<3, number> = [0, 1, 2]      // OK
const t3: TupleN<3, number> = [0, 1, 3, 4]   // Error!
```

# 数値リテラル型のインクリメント
リテラル型の演算で `const num: 1 + 1` なんてことはできないので、代わりにインクリメント型を作りました。
先ほどと同じようにの n個のタプルを作って１つ要素を追加して `length` を取得します。
以降 `Append` は定義してあるものとして省略します。

```typescript
type IncRec<Num, T extends unknown[]> = {
  0: Append<unknown, T>['length']
  1: IncRec<Num, Append<unknown, T>>
}[T['length'] extends Num ? 0 : 1]

// 数値リテラル型に +1 する
type Inc<N extends number> = IncRec<N, []>
```
### 使用例
```typescript
// Inc<10> -> 11
const n1: Inc<10> = 11   // OK
const n2: Inc<10> = 10   // Error!
```

本当は `TupleN` を使いまわせたら良いのですが、直接型引数を再帰型へ渡すと無限再帰になってエラーになります。
またオブジェクトで包む必要があるので面倒で止めました。

```typescript
type Inc<N extends number> = Append<unknown, TupleN<N, unknown>>['length']  // Error!
```

# 数値リテラル型のデクリメント
反対のデクリメントも作りました。
最後に `Append` と逆に `Tail` で要素を１つ取り除いてあげます。

```typescript
// Tuple の先頭以外を取り出す
type Tail<T extends unknown[]> = ((
  ...args: T
) => void) extends ((head: unknown, ...args: infer R) => void)
  ? R
  : never

type DecRec<Num, T extends unknown[]> = {
  0: Tail<T>['length']
  1: DecRec<Num, Append<unknown, T>>
}[T['length'] extends Num ? 0 : 1]

// 数値リテラル型に -1 する
type Dec<N extends number> = DecRec<N, []>
```
### 使用例
```typescript
// Dec<10> -> 9
const n1: Dec<10> = 9    // OK
const n2: Dec<10> = 10   // Error!
```

# n...mの範囲の内１つの数値を表す型
念願の型範囲バリデーションです。


```typescript

// 0...N-1 の Union を作る
type NumberInRangeRec<Num, T extends number, Count extends unknown[]> = {
  0: T
  1: NumberInRangeRec<Num, T | Count['length'], Append<unknown, Count>>
}[Count['length'] extends Num ? 0 : 1]

// n...mの範囲の内１つの数値を表す型
type NumberInRange<N extends number, M extends number> =
  Exclude<NumberInRangeRec<M, 0, []> | M, NumberInRangeRec<N, never, []>>
```

`NumberInRangeRec` で `0...N-1` の Union を作ります。
カウント型変数を再帰毎に Union して１つずつ範囲を広げていきます。

`NumberInRange` の定義側では簡略式で書くと以下のように型を展開しています
1. `(0...m-1 | m) - (never...n-1)` 
2. `(0...m) - (never...n-1)` 
3. `n..m` 

`0...m-1 | m` の部分は `0...N-1` に合わせつつ未満でなく以下にするためですが、
`0...N` を返す `NumberInRangeRec` を作っても良いかもしれません。

`0...n-1` でなく `never...n-1` を使っているのは、 `NumberInRange<0, M>` の指定の際に `0...n = 0...0` では `0` 自体が Union の対象から外れてしまうので、その回避のために使っています。
`never` を渡せば再帰０回ならそのまま `never` が返ってきて Union しても影響がないということですね。
### 使用例
```typescript
// NumberInRange<10, 30> -> 10 | 11 | 12 | ... 30
const n1: NumberInRange<10, 30> = 9    // Error!
const n2: NumberInRange<10, 30> = 20   // OK
const n3: NumberInRange<10, 30> = 31   // Error!
```

# 型再帰型も作りたかった
ついでに、これだけ再帰を書いてると一般化したいんですけど、こんな感じの型アロー関数 `type<T> => Type<T>` 的な何かが無いと無理な気がして諦めました。

```typescript
type Rec<Num extends number, Elm, Target, Transform, Count extends unknown[]> = {
  0: Target
  1: Rec<Num, Elm, Target, Transform<Elm, Target, Count>, Count>
}[Count['length'] extends Num ? 0 : 1]

type TupleN<N extends number, T> = Rec<N, T, [], type<Elm, Target> => Append<Elm, Target>, []>
```


# 残念なお知らせ
ここまで書きましたが、これら全部実用では使えません。
再帰回数の限度があり、自分の場合42回までしか再帰してくれませんでした。
つまり `NumberInRange<0, 100>` は無理でした、`NumberInRange<0, 41>` で限界です。
はい、自己満足です。でも型バリデーションの夢を見たかったのよ。。。
残念😭

でもTypeScriptがパワーアップしてこういう型が実際使えたら楽しいですよね、ならないかなぁ