TypeScriptで整数値の範囲を表現するUnion型をつくるUtility Typeを定義する
TypeScriptである任意の範囲を表現する整数型を定義することを考えます。
たとえば、1~12の範囲を表現する場合、以下のように書くことができます。
type MonthRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
これを以下のように書けると便利です。
type MonthRange = NumberRange<1, 12>
このときの NumberRange
型を定義してみます。
話を簡単にするため、まずは 0 から任意の数までの連続した整数型を持つUnion型をつくる型を定義してみます。
type NumberRange<
U extends number, // NumberRangeArray型に渡す to
Z extends true[] = [], // 繰り返した回数を記録する配列型(初期は空の配列型)
W = 0 // NumberRangeを再帰的に呼ぶので
// 作成したUnion型の途中経過をW型に格納する
> = Z['length'] extends U ? W : NumberRange<U, [...Z, true], W | Z['length']>
U型と配列型Zの長さが一致するまで、W型にその時点での配列型Zの長さを整数型として追加していくという型定義です。
NumberRange
は再帰的に呼び出され、呼び出すたびに配列型Zに要素の型を追加していきます(配列の長さが変わればいいので、適当に true
型の要素を追加しています)。配列型Zの長さとUが一致するまで繰り返されます。
このとき、例えば以下のようにした場合、
type Month = NumberRange<12>
Month
型は 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
型になります。(TypeScript Playgroundで確認する)
NumberRange
型を発展させ、任意の数から任意の数までの連続した整数値を持つUnion型をつくる型に書き換えます。
type ArrayFixedLength<T extends number, U, V extends Array<U> = [], W extends Array<true> = [true]> =
W[T] extends true ? V : ArrayFixedLength<T, U, [...V, U], [...W, true]>
type NumberRange<
T extends number, // NumberRangeArray型に渡す from
U extends number, // NumberRangeArray型に渡す to
Z extends true[] = ArrayFixedLength<T, true>, // 繰り返した回数を記録する配列型
// 予め数列の始点分の配列型をセットしておく
W = T // NumberRangeを再帰的に呼ぶので
// 作成したUnion型の途中経過をW型に格納する
> = Z['length'] extends U ? W : NumberRange<T, U, [...Z, true], W | Z['length']>
(上記ソースコードにある ArrayFixedLength<T, U>
型は、以前作った「任意の要素を持つ配列型」を生成する型定義です。Tに指定した整数値分のU型の配列をつくります。)
NumberRange
型のうち、配列型Zにはあらかじめ初期型として、長さTぶんの配列をセットしておくと、そのぶんNumberRange
を再帰的に呼び出す回数が減ります。かつW型には初期値として数列の開始値Tをセットしておき、連続する整数値の開始時の値としています。
type MonthRange = NumberRange<1, 12>
としたとき、 MonthRange
は 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
型となります。
この型定義では、配列型Zの長さはU型と一致すると再帰呼び出しを終了するので、型「12」は含まれていません。(TypeScript Playgroundで確認する)
これだと使いづらいので、さらに改善していきます。
type NumberRange<
T extends number, // NumberRangeArray型に渡す from
U extends number, // NumberRangeArray型に渡す to
Y extends unknown[] = [...ArrayFixedLength<U, true>, true], // 終了条件を判定する型
Z extends true[] = ArrayFixedLength<T, true>, // 繰り返した回数を記録する配列型
W = T // NumberRangeを再帰的に呼ぶので
// 作成したUnion型の途中経過をW型に格納する
> = Z['length'] extends Y['length'] ? W : NumberRange<T, U, Y, [...Z, true], W | Z['length']>
Y型は、長さ(U+1)の配列型です。配列型Yの長さと配列型Zの長さを比較するように修正することで、先ほどの終了条件(Uと配列型Zの長さ)よりも1回多く処理を実行できます。(TypeScript Playgroundで確認する)
type MonthRange = NumberRangeArray<1, 12>
としたとき、MonthRange
型は 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
型になり、期待した結果が得られました。
たのしい型パズルの様子をお届けしました。ではでは~
このカウンタは @piyoppi/counter-tools を使っています。
クリックすると匿名でいいねできます。