Typescript 当某些参数为泛型或匿名时键入compose

Typescript 当某些参数为泛型或匿名时键入compose,typescript,generics,type-inference,function-composition,Typescript,Generics,Type Inference,Function Composition,我想要一个实现函数合成的compose函数。我希望compose的定义是类型安全的,允许根据需要输入尽可能多的参数,并正确地处理自身为泛型或匿名的参数。这最后一个要求对我来说是个绊脚石 我当前的定义,使用Typescript 4.1候选发行版中的定义: type Compose<Fns extends any[]> = Fns extends [(...args: infer Args) => infer Return] ? (...args: Args) =>

我想要一个实现函数合成的
compose
函数。我希望
compose
的定义是类型安全的,允许根据需要输入尽可能多的参数,并正确地处理自身为泛型或匿名的参数。这最后一个要求对我来说是个绊脚石

我当前的定义,使用Typescript 4.1候选发行版中的定义:

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>;
当传递给
compose
的函数之一是泛型函数时,就会出现问题:

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

const foobar = compose(foo, bar); // typed as `never`
这里,
x
是一个隐式的
any
,而不是由
foo
的返回值所指示的隐式的
number

归根结底,这些问题并不令人惊讶:对一个函数的返回值和下一个函数的参数的限制来自
Compose
,只有在
Compose
上的返回值出现时才会使用。当TS开始处理这个问题时,参数和泛型参数已经被计算过了

我试图重新定义事物,以便在
compose
参数中有一种实际意义,即函数必须是相关的,而不仅仅是允许
((…args:any[])=>any)[
,但到目前为止还没有实现。Typescript继续推断
任何
未知
,而整个键入过程刚好中断

作为参考,以下是该尝试:

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>;
type Composable=Tail扩展推断Tail?尾部延伸任何[]?{
[I in keyof Tail]:我扩展了keyof类型?(arg:Types[I])=>Tail[I]:从不;
}:从不:从不;
声明函数compose(…fns:fns):compose;
Tail
是一种类型,其计算结果为传递的数组类型,但不包括其在此处的第一个成员,
Tail[I]
对应于系列中
Types[I]
之后的下一项)

但这只是将
any[]
用于
T
,然后
Composable
就变成了我以前的
((arg:any)=>any)[
。所以这没有帮助


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

这是一个艰难的问题,因为您试图强制执行
compose
的arguments数组的每个元素都依赖于前面的元素。据我所知,这是办不到的。您需要一个映射类型,其中每个
I
都可以从
I-1
获取返回类型,但这种操作不存在。@LindaPaste我完全可以做到,编写
减法
类型并不太难(无论如何,对于非负整数)我的
Tail
实现基本上是一样的,但不幸的是,它太复杂/间接层次太多,TS无法“理解”您所描述的关系。这是完全可以理解的,但伙计,我真的想要一个方法来做到这一点。
declare function foo(x: string): number;

const foobar = compose(foo, x => x.toLocaleString()); // typed as `(x: string) => any`
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>;