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