TypeScript 型レベル計算:算術演算を型システムで実装する方法

TypeScript の型システムは単なる型チェック機能を超えて、コンパイル時に計算処理を実行できる強力な機能を持っています。今回は、型レベルで算術演算を実装する革新的なテクニックをご紹介します。
実行時ではなく、コンパイル時に数値計算を行うことで、より安全で効率的なコードが書けるようになります。一見不可能に思える型システムでの計算処理を、具体的なコード例とともに詳しく解説していきますね。
型レベル計算の基本概念
TypeScript 型システムの計算可能性
TypeScript の型システムは、チューリング完全な計算能力を持っています。これは、適切な型定義を組み合わせることで、あらゆる計算処理を型レベルで実現できることを意味します。
型レベル計算の核となる機能は以下の通りです:
機能 | 説明 | 用途 |
---|---|---|
Conditional Types | 条件分岐による型選択 | if 文相当の処理 |
Recursive Types | 再帰的な型定義 | ループ処理の実現 |
Template Literal Types | 文字列操作 | 文字列ベースの計算 |
Tuple Types | 配列型の操作 | データ構造の表現 |
typescript// 基本的な条件分岐型
type IsZero<T extends number> = T extends 0 ? true : false;
// 使用例
type Test1 = IsZero<0>; // true
type Test2 = IsZero<5>; // false
この条件分岐型が、より複雑な計算処理の基盤となります。
リテラル型とユニオン型の数値表現
型レベル計算では、数値をリテラル型として扱います。しかし、TypeScript の型システムでは直接的な数値操作ができないため、配列の長さやタプルのインデックスを活用して数値を表現します。
typescript// 数値を配列の長さで表現
type Zero = [];
type One = [unknown];
type Two = [unknown, unknown];
type Three = [unknown, unknown, unknown];
// 配列の長さから数値を取得
type Length<T extends readonly unknown[]> = T['length'];
type TestLength = Length<Three>; // 3
より汎用的な数値表現として、再帰的にタプルを生成する型を定義できます:
typescript// N個の要素を持つタプルを生成
type Tuple<
N extends number,
Result extends unknown[] = []
> = Result['length'] extends N
? Result
: Tuple<N, [...Result, unknown]>;
// 使用例
type FiveTuple = Tuple<5>; // [unknown, unknown, unknown, unknown, unknown]
type FiveLength = Length<FiveTuple>; // 5
この仕組みにより、任意の数値を型レベルで表現できるようになります。
基本的な算術演算の実装
加算(Addition)の型実装
加算は、2 つのタプルを結合することで実現できます。配列のスプレッド演算子を使用して、型レベルでの配列結合を行います。
typescript// 基本的な加算型
type Add<A extends number, B extends number> = [
...Tuple<A>,
...Tuple<B>
]['length'];
// 使用例
type Result1 = Add<3, 5>; // 8
type Result2 = Add<0, 7>; // 7
type Result3 = Add<10, 15>; // 25
より効率的な実装として、再帰を使った加算も可能です:
typescript// 再帰を使った加算(より効率的)
type AddRecursive<
A extends number,
B extends number,
Counter extends unknown[] = []
> = Counter['length'] extends B
? A
: AddRecursive<
A extends number ? A : never,
B,
[...Counter, unknown]
>;
// 実際の加算処理
type AddOptimized<
A extends number,
B extends number
> = AddRecursive<A, B> extends number
? [...Tuple<AddRecursive<A, B>>, ...Tuple<B>]['length']
: never;
この実装により、型レベルでの加算処理が可能になります。
減算(Subtraction)の型実装
減算は加算よりも複雑で、配列から要素を取り除く操作として実装します。
typescript// 減算の実装
type Subtract<
A extends number,
B extends number,
Counter extends unknown[] = []
> = Counter['length'] extends B
? A extends Counter['length']
? 0
: A extends number
? Tuple<A> extends [...Counter, ...infer Rest]
? Rest['length']
: never
: never
: A extends Counter['length']
? never // 負数は表現できない
: Subtract<A, B, [...Counter, unknown]>;
// 使用例
type SubResult1 = Subtract<8, 3>; // 5
type SubResult2 = Subtract<10, 4>; // 6
type SubResult3 = Subtract<5, 5>; // 0
負数を扱う場合は、符号を表現する型を追加で定義する必要があります:
typescript// 符号付き数値の表現
type SignedNumber<
N extends number,
Sign extends 'positive' | 'negative' = 'positive'
> = {
value: N;
sign: Sign;
};
// 符号を考慮した減算
type SignedSubtract<
A extends SignedNumber<number>,
B extends SignedNumber<number>
> = A['sign'] extends 'positive'
? B['sign'] extends 'positive'
? A['value'] extends number
? B['value'] extends number
? Subtract<A['value'], B['value']> extends number
? SignedNumber<Subtract<A['value'], B['value']>>
: SignedNumber<
Subtract<B['value'], A['value']>,
'negative'
>
: never
: never
: SignedNumber<Add<A['value'], B['value']>>
: never;
乗算(Multiplication)の型実装
乗算は、加算を繰り返すことで実装できます。
typescript// 乗算の実装
type Multiply<
A extends number,
B extends number,
Counter extends unknown[] = [],
Accumulator extends unknown[] = []
> = Counter['length'] extends B
? Accumulator['length']
: Multiply<
A,
B,
[...Counter, unknown],
[...Accumulator, ...Tuple<A>]
>;
// 使用例
type MulResult1 = Multiply<3, 4>; // 12
type MulResult2 = Multiply<5, 6>; // 30
type MulResult3 = Multiply<0, 10>; // 0
より効率的な実装として、二進法を活用した乗算も可能です:
typescript// 二進法を使った効率的な乗算
type MultiplyBinary<
A extends number,
B extends number,
Result extends unknown[] = []
> = B extends 0
? Result['length']
: B extends 1
? [...Result, ...Tuple<A>]['length']
: // B が偶数の場合
Divide<B, 2> extends number
? MultiplyBinary<Add<A, A>, Divide<B, 2>, Result>
: // B が奇数の場合
MultiplyBinary<
Add<A, A>,
Divide<Subtract<B, 1>, 2>,
[...Result, ...Tuple<A>]
>;
除算(Division)の型実装
除算は最も複雑な演算で、減算を繰り返して商を求めます。
typescript// 除算の実装(整数除算)
type Divide<
A extends number,
B extends number,
Counter extends unknown[] = []
> = B extends 0
? never // ゼロ除算エラー
: A extends 0
? 0
: Subtract<A, B> extends number
? Subtract<A, B> extends 0
? Add<Counter['length'], 1>
: Divide<Subtract<A, B>, B, [...Counter, unknown]>
: Counter['length']; // A < B の場合
// 使用例
type DivResult1 = Divide<12, 3>; // 4
type DivResult2 = Divide<15, 4>; // 3(整数除算)
type DivResult3 = Divide<8, 2>; // 4
余りを求める剰余演算も実装できます:
typescript// 剰余演算の実装
type Modulo<
A extends number,
B extends number
> = B extends 0
? never
: A extends 0
? 0
: Subtract<A, B> extends number
? Subtract<A, B> extends 0
? 0
: Modulo<Subtract<A, B>, B>
: A; // A < B の場合
// 使用例
type ModResult1 = Modulo<15, 4>; // 3
type ModResult2 = Modulo<20, 6>; // 2
type ModResult3 = Modulo<8, 8>; // 0
高度な計算テクニック
再帰型を使った反復計算
再帰型を活用することで、複雑な反復計算を実装できます。階乗計算を例に見てみましょう。
typescript// 階乗計算の実装
type Factorial<
N extends number,
Counter extends unknown[] = [unknown],
Accumulator extends unknown[] = [unknown]
> = Counter['length'] extends N
? Accumulator['length']
: Factorial<
N,
[...Counter, unknown],
[
...Accumulator,
...Tuple<
Multiply<
Accumulator['length'],
Add<Counter['length'], 1>
>
>
]
>;
// より効率的な階乗実装
type FactorialOptimized<
N extends number,
Current extends number = 1,
Result extends number = 1
> = Current extends N
? Multiply<Result, Current>
: FactorialOptimized<
N,
Add<Current, 1>,
Multiply<Result, Current>
>;
// 使用例
type Fact5 = FactorialOptimized<5>; // 120
type Fact4 = FactorialOptimized<4>; // 24
フィボナッチ数列の計算も実装できます:
typescript// フィボナッチ数列の実装
type Fibonacci<
N extends number,
Current extends number = 0,
A extends number = 0,
B extends number = 1
> = Current extends N
? A
: Fibonacci<N, Add<Current, 1>, B, Add<A, B>>;
// 使用例
type Fib10 = Fibonacci<10>; // 55
type Fib8 = Fibonacci<8>; // 21
条件分岐を使った計算制御
複雑な条件分岐を組み合わせることで、高度な計算ロジックを実装できます。
typescript// 最大公約数(GCD)の実装
type GCD<A extends number, B extends number> = B extends 0
? A
: GCD<B, Modulo<A, B>>;
// 最小公倍数(LCM)の実装
type LCM<A extends number, B extends number> = Divide<
Multiply<A, B>,
GCD<A, B>
>;
// 使用例
type GcdResult = GCD<48, 18>; // 6
type LcmResult = LCM<12, 8>; // 24
素数判定も実装可能です:
typescript// 素数判定の実装
type IsPrime<
N extends number,
Divisor extends number = 2
> = N extends 0 | 1
? false
: N extends 2
? true
: Multiply<Divisor, Divisor> extends number
? Multiply<Divisor, Divisor> extends infer Square
? Square extends number
? Square extends N
? Modulo<N, Divisor> extends 0
? false
: true
: Square extends number
? Modulo<N, Divisor> extends 0
? false
: IsPrime<N, Add<Divisor, 1>>
: never
: never
: never
: never;
// 使用例(小さな数値のみ)
type Prime7 = IsPrime<7>; // true
type Prime9 = IsPrime<9>; // false
Template Literal Types での文字列計算
Template Literal Types を使用して、文字列ベースの計算も実装できます。
typescript// 文字列から数値への変換
type StringToNumber<S extends string> =
S extends `${infer N extends number}` ? N : never;
// 数値から文字列への変換
type NumberToString<N extends number> = `${N}`;
// 文字列での加算
type StringAdd<
A extends string,
B extends string
> = NumberToString<
Add<StringToNumber<A>, StringToNumber<B>>
>;
// 使用例
type StrResult = StringAdd<'5', '3'>; // "8"
16 進数計算も実装できます:
typescript// 16進数の文字列計算
type HexDigit =
| '0'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
| 'A'
| 'B'
| 'C'
| 'D'
| 'E'
| 'F';
type HexToDecimal<H extends string> = H extends '0'
? 0
: H extends '1'
? 1
: H extends '2'
? 2
: H extends '3'
? 3
: H extends '4'
? 4
: H extends '5'
? 5
: H extends '6'
? 6
: H extends '7'
? 7
: H extends '8'
? 8
: H extends '9'
? 9
: H extends 'A'
? 10
: H extends 'B'
? 11
: H extends 'C'
? 12
: H extends 'D'
? 13
: H extends 'E'
? 14
: H extends 'F'
? 15
: never;
// 16進数文字列の加算
type HexAdd<A extends HexDigit, B extends HexDigit> = Add<
HexToDecimal<A>,
HexToDecimal<B>
>;
実践的な活用例
配列長の計算
型レベル計算を使用して、配列操作の型安全性を向上させることができます。
typescript// 配列の長さを制限する型
type FixedLengthArray<T, N extends number> = T[] & {
length: N;
};
// 配列の結合時の長さ計算
type ConcatLength<
A extends readonly unknown[],
B extends readonly unknown[]
> = Add<A['length'], B['length']>;
// 使用例
function concatArrays<
A extends readonly unknown[],
B extends readonly unknown[]
>(
a: A,
b: B
): FixedLengthArray<unknown, ConcatLength<A, B>> {
return [...a, ...b] as any;
}
// 型安全な配列操作
const arr1 = [1, 2, 3] as const;
const arr2 = [4, 5] as const;
const result = concatArrays(arr1, arr2); // length: 5
配列のスライス操作も型レベルで制御できます:
typescript// 配列スライスの型計算
type SliceLength<
Start extends number,
End extends number
> = Subtract<End, Start>;
type SafeSlice<
T extends readonly unknown[],
Start extends number,
End extends number
> = Start extends number
? End extends number
? SliceLength<Start, End> extends number
? FixedLengthArray<T[number], SliceLength<Start, End>>
: never
: never
: never;
// 使用例
function safeSlice<
T extends readonly unknown[],
Start extends number,
End extends number
>(
array: T,
start: Start,
end: End
): SafeSlice<T, Start, End> {
return array.slice(start, end) as any;
}
型レベルでの範囲チェック
数値の範囲を型レベルで検証する仕組みを実装できます。
typescript// 範囲チェック型
type InRange<
Value extends number,
Min extends number,
Max extends number
> = Value extends number
? Min extends number
? Max extends number
? Subtract<Value, Min> extends number
? Subtract<Max, Value> extends number
? true
: false
: false
: never
: never
: never;
// 範囲制限された数値型
type RangedNumber<
Min extends number,
Max extends number
> = number & { __brand: `range-${Min}-${Max}` };
// 範囲チェック関数
function createRangedNumber<
Min extends number,
Max extends number,
Value extends number
>(
min: Min,
max: Max,
value: Value
): InRange<Value, Min, Max> extends true
? RangedNumber<Min, Max>
: never {
if (value >= min && value <= max) {
return value as any;
}
throw new Error(
`Value ${value} is out of range [${min}, ${max}]`
);
}
// 使用例
const validValue = createRangedNumber(1, 10, 5); // OK
// const invalidValue = createRangedNumber(1, 10, 15); // コンパイルエラー
数学的制約の型表現
複雑な数学的制約を型システムで表現できます。
typescript// 偶数・奇数の判定
type IsEven<N extends number> = Modulo<N, 2> extends 0
? true
: false;
type IsOdd<N extends number> = IsEven<N> extends true
? false
: true;
// 偶数のみを受け入れる型
type EvenNumber = number & { __brand: 'even' };
function createEvenNumber<N extends number>(
n: N
): IsEven<N> extends true ? EvenNumber : never {
if (n % 2 === 0) {
return n as any;
}
throw new Error(`${n} is not an even number`);
}
// 完全数の判定(約数の和が自分自身と等しい数)
type Divisors<
N extends number,
Current extends number = 1,
Result extends number[] = []
> = Current extends N
? Result
: Modulo<N, Current> extends 0
? Divisors<N, Add<Current, 1>, [...Result, Current]>
: Divisors<N, Add<Current, 1>, Result>;
type SumArray<
Arr extends number[],
Index extends number = 0,
Sum extends number = 0
> = Index extends Arr['length']
? Sum
: SumArray<Arr, Add<Index, 1>, Add<Sum, Arr[Index]>>;
type IsPerfectNumber<N extends number> = SumArray<
Divisors<N>
> extends N
? true
: false;
// 使用例(小さな数値のみ)
type Perfect6 = IsPerfectNumber<6>; // true (1+2+3=6)
type Perfect28 = IsPerfectNumber<28>; // true (1+2+4+7+14=28)
三角数や平方数の判定も実装できます:
typescript// 三角数の判定(n*(n+1)/2の形で表せる数)
type IsTriangularNumber<
N extends number,
Current extends number = 1
> = Divide<Multiply<Current, Add<Current, 1>>, 2> extends N
? true
: Divide<
Multiply<Current, Add<Current, 1>>,
2
> extends number
? Divide<
Multiply<Current, Add<Current, 1>>,
2
> extends infer Triangle
? Triangle extends number
? Triangle extends N
? true
: N extends number
? Triangle extends number
? Subtract<N, Triangle> extends number
? IsTriangularNumber<N, Add<Current, 1>>
: false
: false
: false
: false
: false
: false;
// 平方数の判定
type IsPerfectSquare<
N extends number,
Current extends number = 1
> = Multiply<Current, Current> extends N
? true
: Multiply<Current, Current> extends number
? Multiply<Current, Current> extends infer Square
? Square extends number
? Square extends N
? true
: N extends number
? Square extends number
? Subtract<N, Square> extends number
? IsPerfectSquare<N, Add<Current, 1>>
: false
: false
: false
: false
: false
: false;
// 使用例
type Triangle10 = IsTriangularNumber<10>; // true (4*5/2=10)
type Square16 = IsPerfectSquare<16>; // true (4*4=16)
まとめ
TypeScript の型システムを使った算術演算の実装について、基本概念から実践的な活用例まで詳しく解説しました。
型レベル計算の主要なポイントをまとめると:
基本技術
- Conditional Types による条件分岐
- Recursive Types による反復処理
- Tuple 操作による数値表現
- Template Literal Types による文字列計算
実装した演算
- 四則演算(加算・減算・乗算・除算)
- 高度な数学関数(階乗・フィボナッチ・GCD・LCM)
- 数学的判定(素数・完全数・三角数・平方数)
実践的な活用
- 配列操作の型安全性向上
- 数値範囲の型レベル検証
- 数学的制約の型システム化
型レベル計算は、コンパイル時に複雑な検証や計算を行うことで、実行時エラーを防ぎ、より堅牢なコードを書くことを可能にします。ただし、複雑すぎる計算はコンパイル時間の増加や TypeScript の型推論限界に達する可能性があるため、実用性とのバランスを考慮して活用することが重要ですね。
これらのテクニックを活用することで、TypeScript の型システムの真の力を引き出し、より安全で表現力豊かなコードが書けるようになるでしょう。