MinusOne
Завдання
Ви отримуєте на вході число(завжди позитивне). Ваш тип повинен повернути те ж число, тільки на одиницю менше. Наприклад:
type Zero = MinusOne<1>; // 0
type FiftyFour = MinusOne<55>; // 54
Розв’язок
Ця задача дійсно доволі складна. TypeScript не може нічого запропонувати для роботи з числами - нічого!
Тому, ми повинні якось найти обхідний шлях для реалізації такої математично операції. І, зрозумійте, що це навряд чи буде відмінне рішення і використовувати його як приклад для наслідування навряд чи вийде.
Я почав з того, що запитав себе. Чи були у нас випадки, коли ми працювали з числовими літералами, але без використання самих літералів. І, як виявилося, так. Ми використовували кортежі.
В нас уже були задачі, в яких ми працювали з кортежами й дізнавалися довжину цих
кортежів. Пам’ятаєте синтаксис для цього? Це було дуже давно - ми зверталися до
властивості length
, яка й повертала нам числовий тип літерал.
Тому я подумав, а що якщо ми створимо кортеж потрібної нам довжини і виведемо його частину без останнього елементу. А після, візьмемо довжину цього кортежу.
Давайте почнемо з допоміжного типу, який буде нам створювати кортеж потрібної
довжини. Назвемо його Tuple
:
type Tuple<L extends number, T extends unknown[] = []> = never;
Він приймає в якості аргументів довжину кортежу і тимчасовий акумулятор. Цей
акумулятор буде накопичувати кортеж, поки ми не отримаємо кортеж потрібної
довжини. Щоб реалізувати цю перевірку, звернемося до властивості length
й
порівняємо його з необхідним:
type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
? never
: never;
Як тільки ми отримаємо кортеж потрібної довжини - повертаємо його:
type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
? T
: never;
Але, якщо ж ми не отримаємо кортеж потрібної довжини, нам потрібно додати до нього ще один елемент. І продовжувати так варто доти, поки не отримаємо очікувану довжину кортежу:
type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
? T
: Tuple<L, [...T, unknown]>;
Тепер, коли ми викличемо наший тип з параметром 5
, наприклад, то ми отримаємо
кортеж довжиною в 5 елементів й типу unknown
. Якщо ж ми звернемося до
властивості length
на цьому кортежі, то ми отримаємо числовий літерал - 5
.
Те що й потрібно було.
Як же дістати літерал 4
з такого кортежу? Вивести кортеж, довжина якого 5, але
без останнього елементу. Іншими словами, кортеж буде коротший на один елемент.
type MinusOne<T extends number> = Tuple<T> extends [...infer L, unknown]
? never
: never;
Використовуючи таку конструкцію, ми отримаємо в тип параметрі L
кортеж без
останнього цього елементу. Все що залишається зробити - це повернути довжину
виведеного кортежу.
type MinusOne<T extends number> = Tuple<T> extends [...infer L, unknown]
? L["length"]
: never;
Таким чином, ми реалізували якусь подобу математичної операції в системі типів.
Наприклад, викликаючи наший тип з параметром 5
, ми отримаємо числовий літерал
4
.
type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
? T
: Tuple<L, [...T, unknown]>;
type MinusOne<T extends number> = Tuple<T> extends [...infer L, unknown]
? L["length"]
: never;
Але! Велике “але”! В останніх версіях TypeScript, вони додали перевірку на кількість рекурсивних викликів. Тому, якщо чесно, ми не пройдемо тести, в яких використовуються числа більше 50. Так що цей розв’язок складно назвати розв’язком.
Якщо у вас є кращі ідеї, не соромтеся, пишіть їх з поясненням в коментарях нижче. Дякую!
Коментарі