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