Комментарии 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;
Да, согласен. Поскольку здесь 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
.
Обобщенные фабрики тайпгардов в TypeScript