[英]Type-safe predicate functions in TypeScript
My goal is to write predicate functions ( isNull
and isUndefined
for example) in TypeScript which fulfill the following conditions:我的目标是在 TypeScript 中编写满足以下条件的谓词函数(例如
isNull
和isUndefined
):
array.filter(isNull)
array.filter(isNull)
array.filter(and(not(isNull), not(isUndefined)))
array.filter(and(not(isNull), not(isUndefined)))
array.filter(isNull)
will be null[]
array.filter(isNull)
的返回类型将为null[]
const isNotNull = not(isNull)
const isNotNull = not(isNull)
The first two conditions are easy to fulfill:前两个条件很容易满足:
type Predicate = (i: any) => boolean;
const and = (p1: Predicate, p2: Predicate) =>
(i: any) => p1(i) && p2(i);
const or = (p1: Predicate, p2: Predicate) =>
(i: any) => p1(i) || p2(i);
const not = (p: Predicate) =>
(i: any) => !p(i);
const isNull = (i: any) =>
i === null;
const isUndefined = (i: any) =>
i === undefined;
const items = [ "foo", null, 123, undefined, true ];
const filtered = items.filter(and(not(isNull), not(isUndefined)));
console.log(filtered);
But because no type-guards are used here TypeScript assumes that the variable filtered
has the same type as items
which is (string,number,boolean,null,undefined)[]
while it actually now should be (string,number,boolean)[]
.但是因为这里没有使用类型保护,TypeScript 假设
filtered
的变量与items
具有相同的类型,即(string,number,boolean,null,undefined)[]
而现在它实际上应该是(string,number,boolean)[]
。
So I added some typescript magic:所以我添加了一些打字稿魔法:
type Diff<T, U> = T extends U ? never : T;
type Predicate<I, O extends I> = (i: I) => i is O;
const and = <I, O1 extends I, O2 extends I>(p1: Predicate<I, O1>, p2: Predicate<I, O2>) =>
(i: I): i is (O1 & O2) => p1(i) && p2(i);
const or = <I, O1 extends I, O2 extends I>(p1: Predicate<I, O1>, p2: Predicate<I, O2>) =>
(i: I): i is (O1 | O2) => p1(i) || p2(i);
const not = <I, O extends I>(p: Predicate<I, O>) =>
(i: I): i is (Diff<I, O>) => !p(i);
const isNull = <I>(i: I | null): i is null =>
i === null;
const isUndefined = <I>(i: I | undefined): i is undefined =>
i === undefined;
Now it seems to work, filtered
is correctly reduced to type (string,number,boolean)[]
.现在它似乎工作,
filtered
正确地减少到类型(string,number,boolean)[]
。
But because not(isNull)
might be used quite often I want to extract this into a new predicate function:但是因为
not(isNull)
可能经常被使用,我想把它提取到一个新的谓词函数中:
const isNotNull = not(isNull);
While this perfectly works at runtime unfortunately it doesn't compile (TypeScript 3.3.3 with strict mode enabled):虽然这在运行时完美运行,但不幸的是它无法编译(TypeScript 3.3.3 启用了严格模式):
Argument of type '<I>(i: I | null) => i is null' is not assignable to parameter of type 'Predicate<{}, {}>'.
Type predicate 'i is null' is not assignable to 'i is {}'.
Type 'null' is not assignable to type '{}'.ts(2345)
So I guess while using the predicates as argument for the arrays filter
method TypeScript can infer the type of I
from the array but when extracting the predicate into a separate function then this no longer works and TypeScript falls back to the base object type {}
which breaks everything.所以我猜在使用谓词作为数组
filter
方法的参数时,TypeScript 可以从数组中推断出I
的类型,但是当将谓词提取到一个单独的函数中时,这不再起作用,并且 TypeScript 回退到基础对象类型{}
打破一切。
Is there a way to fix this?有没有办法来解决这个问题? Some trick to convince TypeScript to stick to the generic type
I
instead of resolving it to {}
when defining the isNotNull
function?在定义
isNotNull
函数时说服 TypeScript 坚持使用泛型类型I
而不是将其解析为{}
一些技巧? Or is this a limitation of TypeScript and can't be done currently?或者这是 TypeScript 的限制,目前无法完成?
Just found my own two year old question here and tried it again with latest TypeScript version (4.3.5) and the problem does no longer exist.刚刚在这里找到了我自己两年前的问题,并使用最新的 TypeScript 版本(4.3.5)再次尝试,问题不再存在。 The following code compiles fine and the types are correctly inferred:
以下代码编译良好,类型正确推断:
type Diff<T, U> = T extends U ? never : T;
type Predicate<I, O extends I> = (i: I) => i is O;
const and = <I, O1 extends I, O2 extends I>(p1: Predicate<I, O1>, p2: Predicate<I, O2>) =>
(i: I): i is (O1 & O2) => p1(i) && p2(i);
const or = <I, O1 extends I, O2 extends I>(p1: Predicate<I, O1>, p2: Predicate<I, O2>) =>
(i: I): i is (O1 | O2) => p1(i) || p2(i);
const not = <I, O extends I>(p: Predicate<I, O>) =>
(i: I): i is (Diff<I, O>) => !p(i);
const isNull = <I>(i: I | null): i is null =>
i === null;
const isUndefined = <I>(i: I | undefined): i is undefined =>
i === undefined;
const isNotNull = not(isNull);
const isNotUndefined = not(isUndefined);
const items = [ "foo", null, 123, undefined, true ];
const filtered = items.filter(and(isNotNull, isNotUndefined));
console.log(filtered);
这是你要找的吗?
const isNotNull = not(<Predicate<any, null>>isNull);
Pass type information from context.从上下文传递类型信息。 This code is compiled well
这段代码编译得很好
// c: (string | number)[]
let c = [1, 2, 'b', 'a', null].filter(not<number | string | null, null>(isNull));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.