任意数量的通用参数
Posted
技术标签:
【中文标题】任意数量的通用参数【英文标题】:Arbitrary amount of generic arguments 【发布时间】:2021-08-26 09:29:12 【问题描述】:考虑任意数量的函数,每个函数都接受一个参数并返回一个值(不要担心它们的作用,而不是重点):
function toNumber(input: string): number
return parseInt(input)
function toBoolean(input: number): boolean
return input > 0
function toString(input: boolean): string
return input.toString()
这些函数可以像这样链接起来(在 fp 中可能有更好的术语),因为每个函数都将前一个的返回类型作为参数:
type Fn<I, O> = (input: I) => O
function chain(...fns: Fn<any, any>[]): Fn<any, any>
return (input: any) =>
let result = input
for (const fn of fns)
result = fn(result)
return result
const chained = chain(toNumber, toBoolean, toString)
const result = chained('5') // returns "true"
打字稿中有没有办法使chain
函数类型安全?由于我们有任意数量的函数,因此必须有任意数量的泛型参数。
我可以做这样的事情(固定数量的重载):
declare function chain<S, T1>(fn: Fn<S, T1>): Fn<S, T1>
declare function chain<S, T1, T2>(fn1: Fn<S, T1>, fn2: Fn<T1, T2>): Fn<S, T2>
declare function chain<S, T1, T2, T3>(fn1: Fn<S, T1>, fn2: Fn<T1, T2>, fn3: Fn<T2, T3>): Fn<S, T3>
但是有更好的方法吗?
【问题讨论】:
【参考方案1】:我不确定我是否会称其为更好 方式,但可能 将此规则编码到类型系统中。然而,如果没有良好的文档,大多数观察者可能很难理解这是做什么的。
以下方法使用extends ReadonlyArray<Fn<any, any>>
的通用参数来实现“任意数量的通用参数”。
首先,有几个实用程序类型可以帮助处理数组和函数:
type Fn<I, O> = (input: I) => O;
type Input<F> = F extends Fn<infer U, any> ? U : never;
// or you can use ReturnType<T>, which is provided by typescript
type Output<F> = F extends Fn<any, infer U> ? U : never;
// first elem of tuple type
type Head<T> = T extends [infer U, ...unknown[]] ? U : never;
// all but first elem of tuple type
type Tail<T> = T extends [unknown, ...(infer U)] ? U : never;
// last element of array, only works on typescript 4.2+
type Last<T> = T extends [...unknown[], infer U] ? U : never;
接下来,检查两个函数是否可以组合的类型
// `true` if T can be composed with U, `false` if not, and `never` if the types are wrong
type CanCompose<T, U> = T extends Fn<any, infer Output>
? (
U extends Fn<infer Input, any>
? (Input extends Output ? true : false)
: never
)
: never;
一种检查整个函数列表是否可以相互组合的类型
// Evaluates to a tuple of booleans. If any element in the tuple is false, you cannot compose the whole array of T.
type IsComposable<T> = T extends Fn<any, any>[]
? CanCompose<Head<T>, Head<Tail<T>>> extends true
? (
Tail<T> extends never[]
? [true]
: [true, ...(IsComposable<Tail<T>>)]
)
: [false]
: [false];
最后是chain
函数的rest参数的类型和返回值
type Composable<T> = IsComposable<T> extends true[] ? T : never[];
type ComposedFunction<T> = IsComposable<T> extends true[]
? Fn<Input<Head<T>>, Output<Last<T>>>
: Fn<never, never>;
chain 函数现在获得了更令人兴奋的签名,并且在主体中进行了一些类型转换。不幸的是,我不确定如何摆脱类型转换。
function chain<T extends ReadonlyArray<Fn<any, any>>>(...fns: Composable<T>): ComposedFunction<T>
return ((x: Input<Head<T>>) =>
let result = x;
for (const fn of fns)
result = fn(result);
return result;
) as ComposedFunction<T>
这种方法的一个缺点是,如果用户代码违反了类型,他们会得到一个神秘的编译错误,即chain
的第一个参数的类型不能分配给never
。
Link to TS playground with all of this stuff in it
【讨论】:
太棒了,关于类型系统我不知道的东西太多了...以上是关于任意数量的通用参数的主要内容,如果未能解决你的问题,请参考以下文章