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;
Комментарии