typescript / expert
Snippet
Tupel mit fixer Länge durch rekursive Conditional-Inference
`Array<T>` und `T[]` tragen keine Längeninformation auf Typebene — eine Funktion für 3-Vektoren kann zur Compile-Zeit keinen 2-Vektor ablehnen. Der endrekursive `Tuple<T, N, R>`-Builder sammelt Elemente in `R`, bis `R['length']` literal `N` entspricht, und gibt dann `R` selbst zurück — ein Tupel-Typ mit fester Länge. Das Default-Argument `R extends T[] = []` ist der Rekursions-Seed und der Grund, warum Aufrufer nur `Tuple<T, N>` schreiben. TypeScript begrenzt rekursive Instanziierungen auf 1000 — bis dahin funktioniert das; darüber braucht man eine Divide-and-Conquer-Verdopplung.
snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Tuple<T, N extends number, R extends T[] = []> =R['length'] extends N ? R : Tuple<T, N, [...R, T]>;type Vec3 = Tuple<number, 3>; // [number, number, number]type RGB = Tuple<0 | 128 | 255, 3>; // strictly 3 channelsfunction dot<N extends number>(a: Tuple<number, N>,b: Tuple<number, N>): number {return a.reduce((s, x, i) => s + x * b[i], 0);}const v: Vec3 = [1, 2, 3];const w: Vec3 = [4, 5, 6];dot(v, w); // 32// dot(v, [1, 2] as const);// ^^^^^^^^^^^^^^^^^// Source has 2 element(s) but target requires 3.
Erklärung
1
type Tuple<T, N extends number, R extends T[] = []>
Drei Typparameter: Element-Typ, Ziel-Länge und der Akkumulator mit Default `[]`, damit Aufrufer ihn weglassen.
2
R['length'] extends N ? R : Tuple<T, N, [...R, T]>
Basisfall: stimmt die literale Länge des Akkumulators mit `N` überein, gib ihn zurück; sonst rekursiv ein Element via Tuple-Spread anhängen.
3
type Vec3 = Tuple<number, 3>;
Löst zu `[number, number, number]` auf — falsch lange Literale sind nun Compile-Fehler.
4
function dot<N extends number>(a: Tuple<number, N>, b: Tuple<number, N>)
`N` wird aus `a` inferiert und zwingt `b` auf dieselbe Länge — falsche Stelligkeit scheitert an der Aufrufstelle.