こんにちは。
最近、TypeScriptを用いてサーバーサイドの開発を行う機会が増えてきました。
TypeScriptを十分に活用すれば、フロントエンドからサーバーサイドまで一つの言語で構築が可能です。
その上、AWS CDKのようなツールを使用すると、インフラもTypeScriptで設計でき、全てをTypeScriptで完結させるフルスタック開発が実現します。
しかし、このように便利な言語である一方で、サーバーサイドや他の低レイヤーの開発において、TypeScriptのnumber型は幅広すぎる場合があります。
特定の数値範囲、奇数だけ、またはゼロを除外したいなど、厳しい要件が必要な場面が往々にして発生します。
そこで、そのような要件に対応するためのRange型を導入し、開発者のミスを減少させる方法を共有します。
TypeScriptのnumber型
TypeScriptのnumber型の基本的な変数定義は以下のように書きます。
const hoge: number = 1;
この方法で問題は生じませんが、number型の一般性ゆえの制約も存在します。例えば、入力が0の場合にプログラムがクラッシュするリスクが考えられます。
これを避けるため、コード入力段階での厳格な型チェックを実施することで、実行前に問題を検出できます。
もちろん、型がすべてのランタイム値を制御するわけではありませんが、問題の発生率を大きく減少させる助けとなります。
Rangeの型を定義する
定義されたのがこちらです。
このジェネリクスは、startからendまでの数値を型として表現するためのものであり、具体的には、NumericRange<2, 4> のように指定すると、型として2 | 3 | 4を得ることができます。
type NumericRange<
start extends number,
end extends number,
arr extends unknown[] = [],
acc extends number = never
> = arr['length'] extends end
? acc | start | end
: NumericRange<start, end, [...arr, 1], arr[start] extends undefined ? acc: acc | arr['length']>
一つひとつ分解して説明します。
引数
- start extends number : 範囲の開始値
- end extends number : 範囲の終了値
- arr extends unknown[] : 内部で利用される配列。この配列の長さによって現在の数値を判断します。
- acc extends number : 累積値。これまでの数値の範囲を持たせます。
条件の分岐
次は条件分岐について説明します。
arr['length'] extends end
? acc | start | end
: NumericRange<start, end, [...arr, 1], arr[start] extends undefined ? acc : acc | arr['length']>arr['length'] extends end は配列の長さがendに達しているかを判断する条件です。
- true: もし達していれば、それまでの累積値
acc、開始値start、終了値endを結合して型を返します。 - false: それ以外の場合: 配列の長さを1つ増やし
[[...arr, 1]、次の数値の範囲を計算します。この際、現在の数値はarr['length']として取得できます。
例NumericRange<2, 4> を考えた場合
- 最初は
arrが空の配列なので、arr['length']は0です。 - arr['length'] がend(4) に達していないので、再帰的にNumericRangeを呼び出します。このとき、arr は[1]となります。
- このプロセスを繰り返し、
arr['length']が2, 3, 4と増えていきます。 -
arr['length']が4になった時点で、条件arr['length'] extends endが成立し、2 | 3 | 4という型が結果として得られます。
どうでしょう、細かく分ければ理解しやすいですよね。
まとめ
今回は、number 型で数値の範囲を指定するRangeの型を作って解説してみました。
最終的な判断は、将来のコードの厳密さや保守性をどれだけ重視するかによります。
特に他の開発者向けにツールを開発する場合など、このような細かな型の扱いが重要になることがあります。
一方で、エンドユーザー向けの開発では、このようなスキルが必ずしも必要ではないかもしれません。
TypeScriptを使った開発は多岐に渡り、どのように利用するかはプロジェクトの要件や目的に応じて柔軟に選ぶことができるのが強みですのでご参考までに。