Zagadnienie 11 z 12
Krotki wariadyczne
Krotki to jedyna struktura danych na poziomie typów, która ma długość — a więc jedyny licznik. Każda arytmetyka na typach, jaką zobaczysz, to przebrane liczenie elementów krotki.
Rozwinięcia łączą
type A = [1, 2] type B = [3, 4] type Joined = [...A, ...B] // [1, 2, 3, 4] type Prefixed = [0, ...A] // [0, 1, 2] type Len = Joined['length'] // 4 — a numeric literal type
Dla krotki o stałej długości `length` jest literałem liczbowym, a nie `number`. Ten jeden fakt umożliwia arytmetykę: zbuduj krotkę odpowiedniego rozmiaru, a potem odczytaj jej długość.
Dodawanie to złączenie, odejmowanie to dopasowanie
type Build<N extends number, Acc extends unknown[] = []> = Acc['length'] extends N ? Acc : Build<N, [...Acc, unknown]> type Add<A extends number, B extends number> = [...Build<A>, ...Build<B>]['length'] type Sub<A extends number, B extends number> = Build<A> extends [...Build<B>, ...infer Rest] ? Rest['length'] : never
Budowanie krotki dodaje; rozkładanie odejmuje. Akumulator trzyma `unknown`, bo zawartość nie ma znaczenia — to zbiór kresek, a nie kolekcja czegokolwiek.
Element rest nie musi być ostatni
type Last<T> = T extends readonly [...unknown[], infer L] ? L : never type Init<T> = T extends readonly [...infer I, unknown] ? I : never
Wzorzec krotki może zawierać dokładnie jeden element rest i wolno mu stać na początku. Tak dociera się do końca krotki bez liczenia.
Najczęstsza błędna odpowiedź
// Intent: subtract. type Sub<A extends number, B extends number> = [...Build<A>, ...Build<B>]['length'] // That is addition with a different name — concatenating // can only ever make the tuple longer.
Odejmowanie musi krotkę rozłożyć, czyli wymaga dopasowania wzorca, a nie budowania. Jeśli Twój typ arytmetyczny tylko rozwija, potrafi tylko dodawać.
Do zapamiętania
Długość krotki to jedyna liczba, którą system typów potrafi liczyć. Buduj, by dodać, rozkładaj, by odjąć, i spodziewaj się limitu rekurencji w okolicach tysiąca.