Как стать автором
Обновить

Комментарии 6

Интересная статья, спасибо.

Применение any вместо never в качестве типа по умолчанию — вынужденная необходимость из-за ограничения T extends P, вытекающего из природы тайпгарда

можно заменить на unknown

В нашей реализации есть что улучшать. Например, можно попробовать ввести обобщенный тип S, как и в других примерах, чтобы на этапе компиляции отсекать бессмысленные проверки, которые априори ведут к пустому массиву.

Это стоит сделать, потому что прямо сейчас добавление "лишнего" пункта в someOf молча ломает типизацию, и фильтр перестает работать в режиме тайпгарда. Если в самом последнем примере добавить в копилку, допустим, is(2), то filtered будет Event[]

Допилил этот кейс, небольшие изменения в строках 41-48

можно заменить на unknown

В данном случае заменить на unknown нельзя в силу контравариантности параметров функции. При P = unknown тип Guard не будет включать все тайпгарды, тип аргумента которых уже, чем unknown. Проиллюстрирую на примере:

type Guard<P = unknown, T extends P = P> = (x: P) => x is T;

// ✅ Ok
const guard1: Guard = (x: unknown): x is 'a' => true;

// ❌ Type '(x: string) => x is "a"' is not assignable to type 'Guard<unknown, unknown>'.
const guard2: Guard = (x: string): x is 'a' => true;

Ссылка на Playground

Да, согласен. Поскольку здесь any требуется для бивариантности параметра, то ещё можно это указать явно:

type Guard<P = unknown, T extends P = P> = {f(x: P): x is T}['f'];

а в SomeOf уже добавить проверку для P. И never в типе SomeOf я бы поменял на (x: never) => x is never, чтобы нельзя было передать в тот же Array.filter или ещё куда. Итого:

type Guard<P = unknown, T extends P = P> = {f(x: P): x is T}['f'];
type Param<F> = [F] extends [(x: infer P) => unknown] ? P : never;

type SomeOf<T extends Guard[]> =
  T[number] extends Guard<infer P extends Param<T[number]>, infer R>
    ? (x: P) => x is R
    : (x: never) => x is never;

function someOf<T extends Guard[]>(...guards: T) {
    return ((x: never) => guards.some(guard => guard(x))) as SomeOf<T>;
}

----

Вообще типизация метода Array.filter вызывает вопросы. Например, вот такой простой кейс:

const numArr: (1 | 2 | 3)[] = [];
const guard = (x: number): x is 3 | 4 => true;
const filtered = numArr.filter(guard); // (2 | 1 | 3)[]

Здесь filtered по логике должен быть 3[], но увы. Кажется, правильнее было бы как-то так:

filter<S>(predicate: (value: S | Exclude<T, S>) => value is S, thisArg?: any): (S & T)[];

и тогда по идее мои поправки из первого поста будут лишними.

НЛО прилетело и опубликовало эту надпись здесь

Можете это опубликовать в NPM? Было бы круто подключить это всё в свои проекты. А так очень полезная статья ?️

Стоит отметить, что подобные штуки потеряют актуальность в следующем TypeScript 5.5, когда наконец-то завезут вывод предикатов и их нормальную композицию. Сейчас с этим можно поиграться в typescript@next.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий