1️⃣ Range型に関心があるのでConditional Typesよう分からんけど解読してみる 🔟

小ネタです。

# 元ネタ
[typescript - Is it possible to restrict number to a certain range - Stack Overflow](https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range)

# Range型とは
普段数字を扱うのにnumber型を使いますが、更に制約の厳しい、**指定した最小～最大の値の範囲内である**ことを型レベルで保障してくれる型が欲しくなりました。
一昔前、検索してみたところStackOverFlowに

```ts
type OneToTen = 1|2|3|4|5|6|7|8|9|10
```

こんな感じでベタ書きゴリ押しで定義するやり方が乗っていて爆笑しました。ただ全部手書きは明らかに面倒だし、数が多い場合に限界があるのでどうにかならないのかなと感じました。
あれから時は経ち……


# 回答例(TypeScript ver4.5以降)
解説しづらいので型引数の名前を改名してあります。
```ts
type Enumerate<Max extends number, IncrementalNumbers extends number[] = []> 
= IncrementalNumbers['length'] extends Max
  ? IncrementalNumbers[number]
  : Enumerate<Max, [...IncrementalNumbers, IncrementalNumbers['length']]>

type IntRange<Min extends number, Max extends number> = 
Exclude<Enumerate<Max>, Enumerate<Min>> | Max

type TwentyToThreeHundred = IntRange<20, 300>
```
コピペしてみたところ、IntRange型に渡した数字の区間のUnionが返ってきました。自動でUnionを作ってくれるのなら魅力的なので解読してみることにしてみました。
本来粒度の小さい`Enumerate`型から解読すべきとは思うのですが、これに関しては逆に大元から読んだ方がいいと思ったので最下段の`TwentyToThreeHundred`型から読んでいきます。
## TwentyToThreeHundred型
```ts
type TwentyToThreeHundred = IntRange<20, 300>
```
これは`IntRange`型にジェネリクスで2つの型引数を渡しています。おそらく最小値と最大値ですね。
## IntRange型
```ts
type IntRange<Min extends number, Max extends number> = 
Exclude<Enumerate<Max>, Enumerate<Min>> | Max
```
次はこちら。ジェネリクス内で`extends`キーワードを使うことで、制限をかけたり、条件判定したりすることができます。今回は`number`型の制限かけてるだけですね。
そしてここで注目してほしいのが、
```ts
Exclude<Enumerate<Max>, Enumerate<Min>> | Max
```
の部分。```Exclude```型は[Utility Typesの一種で、最初の型引数として渡したUnion型から後の型引数で渡したUnion型を除く型を作る](https://typescriptbook.jp/reference/type-reuse/utility-types/exclude)型です。
`Enumerate`型の実装をまだ見てないので推測ですが、「0～最大値までのUnion」から「0～最小値のUnion」を引いて目的のUnionを作っているようですね。
（2023/06/16追記）
元々Exclude云々しか載せてなかったのですが、このままだと「以上/**未満**」のUnionが出来上がるため、0～9のUnionを作るのに`IntRange<0,10>`という形で定義する必要があります。
直感的に「以上/**以下**」にするにどうしたらいいのかと、元ネタのStackOverFlowを調べたところ、コメント欄の中に記載があり、素直にMaxを付け加えるのがいいとのことです。

## Enumerate型
```ts
type Enumerate<Max extends number, IncrementalNumbers extends number[] = []> 
= IncrementalNumbers['length'] extends Max
  ? IncrementalNumbers[number]
  : Enumerate<Max, [...IncrementalNumbers, IncrementalNumbers['length']]>
```
出たよCondtional Types。何やってるかさっぱり分からん……。😨（クソ雑魚）というわけで当記事の最大の山場です。1行ずつ見ていきましょう。

### 型引数（1行目）
```ts
type Enumerate<Max extends number, IncrementalNumbers extends number[] = []> 
```
第1型引数`Max`は数字、そして第2型引数`IncrementalNumbers`は数字配列で、**デフォルト型引数は空配列**として設定されているようですね。
先ほど`IntRange`型を見たときは`Enumerate`型に**第2型引数を渡してなかった**ので、どうやらあえてデフォルト引数を用いる形で使うのがポイントになりそうです。

### 条件判定（2行目）
```ts
IncrementalNumbers['length'] extends Max
```
型の世界（型レベルプログラミング）においては`extends`と`?`で条件判定して`:`を使って分岐することができるので、ここは普通のプログラミングならばif構文の条件部分にあたる箇所になります。
まず`IncrementalNumbers['length']`とか型の世界（型レベルプログラミング）なのにいきなりプロパティにアクセスしてて面食らいますが、これは[インデックスアクセス型](https://typescriptbook.jp/reference/type-reuse/indexed-access-types)という型で、オブジェクトの特定のプロパティの型を参照するのに使うやつですね。
次に引っ掛かるのが`Array`型の`length`プロパティって`number`型じゃないの？という点ですが、実際にサンプルコードを書いて試してみましょう。
```ts
type T = [1,2,3]
type TLength = T['length']
```
`TLength`型は`number`型になるでしょうか。[答えはWebで！](https://www.typescriptlang.org/play?#code/C4TwDgpgBAKlC8UDaBGANAJjQZgLoChRJYAZCAOwHNgALBWJAcgBsLqbGCg)これはたまげたなぁ……😮
……というわけで第2型引数の配列の長さが厳密に推論され取得できます。1行目でデフォルト引数を使っているのがポイントと書いた通り、初回は空配列=長さは0なので、`0`型となります。
また`Max`型は今回のサンプルだと`20`（ないし`300`）が渡ってくるので実際に行われている条件判定は以下のようになります。
```ts
// 初回
0 extends 20
```
`0`型が`20`型を継承してるかという判定を行っています。TS初心者だと一瞬面食らう質問かもしれませんが、ここは直感を信じてfalseが正解となります。
ここまで解読出来たら何となく先が読めてきたのではないでしょうか。

### 条件判定でfalseだった場合の処理(4行目)
3行目は飛ばして4行目から行きます。大半のループではこっちに入るので。
```ts
Enumerate<Max, [...IncrementalNumbers, IncrementalNumbers['length']]>
```
はい、型の世界でも再帰することができます。第1型引数は同じ値を渡し、第2型引数に今度は元の配列に長さの数字を加えたものを渡しています。
初回の場合は`IncrementalNumbers`型は空配列なので、第2型引数には空配列ではなく`[0]型`を渡すことになります。 **初回の時と違ってますね？** 
ここがポイントで、lengthで配列の長さを取って追加することで、**再帰の度にIncrementalNumbersの長さが伸びていき、疑似的なループ装置**としています。
混乱する人もいると思うので具体例を出すと2回目（再帰1回目）はIncrementalNumbers型は`[0]`型でlengthは**1**となり、3回目に向けて第2引数には`[0,1]`型が渡されることになります。そして3回目のIncrementalNumbers型は`[0,1]型`で、lengthは**2**……と続きます。

### 条件判定でtrueだった場合の処理(3行目)
というわけで延々と`IncrementalNumbers`型を伸ばしていき、2行目の条件判定部分で`Max`型を継承……実質イコールかどうかをチェックしているのですが、見事一致した場合はここで無限再帰から脱出します。
```ts
IncrementalNumbers[number]
```
いきなり`[number]`というワードが出てきて驚く方もいらっしゃるかもしれませんが、これも[インデックスアクセス型の機能の一つ](https://typescriptbook.jp/reference/type-reuse/indexed-access-types#%E9%85%8D%E5%88%97%E5%9E%8B%E3%81%A8%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%9E%8B)で、配列型の中の要素を列挙したUnion型として取得することができます。理屈ぽく言うと「配列に数字でアクセスした際に取得しうる値（→配列内の全要素のうち**どれか**→全要素のUnion）」を取得するって感じですかね～。
IncrementalNumbers型はこの際`[0,1,2,3....]`という感じの型となっているので、条件判定と合わせて、めでたく「0～第1型引数`Max`として指定した値までのUnion」を取得することができます。

# 感想
こんなんパッと見で分かるかー！型の道はかくも厳しい……⛰
ちなみに[元ネタ](#元ネタ)のツリーには更なる改良版も掲載されているっぽいので、気になる方はチェックしてください。