AnyOf
Завдання
Реалізувати функцію any
із мови Python на рівні типів. Тип приймає кортеж й
повертає true
, якщо будь-який із елементів кортежу true
. Якщо ж кортеж
порожній, то повертаємо false
. Наприклад:
type Sample1 = AnyOf<[1, "", false, [], {}]>; // expected to be true
type Sample2 = AnyOf<[0, "", false, [], {}]>; // expected to be false
Розв’язок
Моя перша ідея була використати дистрибутивні умовні типи.
Ми можемо використати синтаксис T[number]
для того, щоб взяти об’єднання всіх
елементів з кортежу. Маючи об’єднання елементів, переберемо кожен із них, й на
кожній ітерації будемо повертати false
або true
в залежності від типу
елемента. У випадку, якщо всі ітерації повернуть false
, ми отримаємо тип
false
. Але, якщо у нас буде хоч один true
, то ми отримаємо boolean
. Тому
що false | true = boolean
. Перевіряючи що ми отримали в результаті, false
чи
boolean
, ми можемо зрозуміти, чи є true
в об’єднанні.
Але, як виявилося, реалізація такого підходу виглядає не дуже добре. Оцініть самі:
type AnyOf<T extends readonly any[], I = T[number]> = (
I extends any ? (I extends Falsy ? false : true) : never
) extends false
? false
: true;
Тому я задумався, а чи можемо ми вирішити це простішим способом? Щоб, хоч якось, можна було зрозуміти, що відбувається, і не загубитися в майбутньому. Виявляється, ми можемо!
Давайте згадувати про виведення типів в кортежах і про варіативні типи. Пам’ятаєте, ми їх використовували в розв’язках таких задач як Last чи Pop і їм подібні.
Почнемо з виведення одного елементу із кортежу й решти, що є в хвості:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? never
: never;
Як ми можемо перевірити, що наший H
це false
? Для початку, давайте створимо
новий тип, в якому вкажемо елементи, які ми вважаємо false
:
type Falsy = 0 | "" | false | [] | { [P in any]: never };
Маючи такий тип, ми можемо використати умовний тип для перевірки, чи входить H
в їх число:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? never
: never
: never;
Що нам робити, якщо тип виявився false
? Для нашого завдання це означає, що
true
ще не знайдено й ми продовжимо його шукати. Реалізуємо це через
рекурсивний виклик типу з хвостом із кортежу, в якості аргументу.
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: never
: never;
Підходимо все ближче до розв’язку проблеми. Що якщо елемент H
це не false
?
Тоді це true
, ми знайшли його! Якщо ми знайшли true
в кортежі, то
продовжувати немає сенсу й ми просто повертаємо true
.
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: never;
Останній стан, який у ми ще не опрацювали - порожній кортеж. У випадку з
порожнім кортежом, наше виведення не спрацює. В такому випадку, як і вказано в
умові завдання, ми повертаємо false
.
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: false;
Таким чином ми реалізували функцію any
з Python на рівні системи типів у
TypeScript. Ось повний розв’язок завдання:
type Falsy = 0 | "" | false | [] | { [P in any]: never };
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: false;
Коментарі