首页 > 解决方案 > 当某些参数是通用的或匿名的时键入 compose

问题描述

我想要一个compose实现函数组合的函数。我希望 of 的定义compose是类型安全的,允许尽可能多的参数,并处理本身是通用或匿名的参数以正确输入。最后一个要求对我来说是一个绊脚石。

我当前的定义,使用Typescript 4.1 Release Candidate 中的递归条件类型:

type Compose<Fns extends any[]> =
    Fns extends [(...args: infer Args) => infer Return] ? (...args: Args) => Return :
    Fns extends [(...args: infer Args0) => infer Ret0, (arg: infer Arg1) => Ret1, ...infer Rest] ? (
        [Ret0, Arg1] extends [Arg1, Ret0] ? Compose<[(...args: Args0) => Ret1, ...Rest]> :
        never
    ) :
    never;

declare function compose<Fns extends ((...args: any[]) => any)[]>(
    ...fns: Fns
): Compose<Fns>;

当所有函数都有固定的、定义的类型时,这非常有效:

declare function foo(x1: string, x2: number): number;
declare function bar(y: number): boolean;
declare function baz(z: boolean): string;

const foobarbaz = compose(foo, bar, baz); // (x1: string, x2: number) => string

当传递给的函数之一compose是通用的时,问题就出现了:

declare function foo(x: string): number;
declare function bar<T>(foo: T): string;

const foobar = compose(foo, bar); // typed as `never`

在这里,foobarnever因为[Arg1, Ret0] extends [Ret0, Arg1]签入Compose失败。该检查失败,因为bar' 的T参数,因此Arg1,被推断为unknown,并且[unknown, number]确实没有扩展[number, unknown]。但当然,T(and Arg1)可能是 number,在这种情况下[number, number] extends [number, number]会通过。在很多情况下,Typescript 会自动推断出这样的泛型参数,但这里不会。

使用匿名函数可以看到同样的问题:

declare function foo(x: string): number;

const foobar = compose(foo, x => x.toLocaleString()); // typed as `(x: string) => any`

这里,x是一个隐含的any,而不是隐含number的,如 的返回值所示foo

归根结底,这些问题并不令人惊讶:一个函数的返回值和下一个函数的参数的限制来自Compose,直到返回值 on 才使用compose。当 TS 开始处理这个问题时,参数和泛型参数已经被评估了。

我试图重新定义事物,以便在compose函数必须相关的论点中有实际意义,而不仅仅是允许((...args: any[]) => any)[],但到目前为止还没有得到任何地方。打字稿继续只是推断anyunknown,整体打字只是中断。

作为参考,这是一次尝试:

type Composable<Types extends any[]> = Tail<Types> extends infer Tail ? Tail extends any[] ? {
    [I in keyof Tail]: I extends keyof Types ? (arg: Types[I]) => Tail[I] : never;
} : never : never;

declare function compose<Fns extends Composable<T>, T extends any[]>(...fns: Fns): Compose<Fns>;

Tail是一种评估传递的数组类型的类型,没有它的第一个成员——在这里,Tail[I]对应于系列中的下一项Types[I]

但这仅用于any[]for T,然后Composable<T>最终与((arg: any) => any)[]我之前的相同。所以没有任何帮助。

在不尝试使用条件类型的情况下,我们可以编写非可变版本来compose解决这个问题(因为每个参数都是根据前一个参数定义的),但是它们只能处理一些硬编码的函数。这很可能是我将采用的解决方案,但我对这个问题的目标是避免这种情况。

标签: typescriptgenericstype-inferencefunction-composition

解决方案


推荐阅读