简体   繁体   English

TypeScript 中的类型安全谓词函数

[英]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 中编写满足以下条件的谓词函数(例如isNullisUndefined ):

  1. Can be used standalone: array.filter(isNull)可以独立使用: array.filter(isNull)
  2. Can be logically combined: array.filter(and(not(isNull), not(isUndefined)))可以逻辑组合: array.filter(and(not(isNull), not(isUndefined)))
  3. Uses Type-guards so TypeScript knows for example that the return type of array.filter(isNull) will be null[]使用类型保护,因此 TypeScript 知道例如array.filter(isNull)的返回类型将为null[]
  4. Combined predicates can be extracted into new predicate functions without breaking type inference: 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM