【发布时间】:2020-12-16 07:55:48
【问题描述】:
TypeScript 4 发行说明的example 展示了如何使用可变元组类型来避免多个重载定义。我想应该可以为任意数量的参数键入这个pipe 函数
type F<P, R> = (p: P) => R
type Pipe2<T1, T2, R> = [F<T1, T2>, F<T2, R>]
type Pipe3<T1, T2, T3, R> = [F<T1, T2>, ...Pipe2<T2, T3, R>]
type Pipe4<T1, T2, T3, T4, R> = [F<T1, T2>, ...Pipe3<T2, T3, T4, R>]
function pipe<T1, R>(f1: F<T1, R>): F<T1, R>
function pipe<T1, T2, R>(...fns: Pipe2<T1, T2, R>): F<T1, R>
function pipe<T1, T2, T3, R>(...fns: Pipe3<T1, T2, T3, R>): F<T1, R>
function pipe<T1, T2, T3, T4, R>(...fns: Pipe4<T1, T2, T3, T4, R>): F<T1, R>
function pipe(...fns) {
return x => fns.reduce((res, f) => f(res), x)
}
一个基本的开始可以是
function pipe<Fns>(...fns: PipeArgs<Fns>): PipeReturn<Fns>
function pipe(...fns) {
return x => fns.reduce((res, f) => f(res), x)
}
帮助器类型 PipeArgs<Fns> 和 PipeReturn<Fns> 的定义仍然缺失。如何定义它们或是否有其他方法?
编辑:我不再那么自信了,但是(TypeScript 4.1.2)。主要问题是其余参数。必须推断pipe 的其余参数fns 的(元组)类型,但必须确保特定的(循环?)结构。这是我目前的方法(使用有效的PipeReturn<Fns>)
type AssertReturn<E, _A extends E, R> = R
type Return<F> =
F extends ((...args: any[]) => infer R)
? R
: never
type Length<L extends any[]> = L['length']
type Tail<L extends any[]> =
L extends readonly [any, ...infer LTail]
? LTail
: L
type Last<L extends any[]> = L[Length<Tail<L>>]
type F<P, R> = (p: P) => R
type PipeArgs<Fns> =
Fns extends readonly [F<infer X, infer Y>, ...infer T]
? T extends readonly [F<any, any>, ...any]
? [F<X, Y>, ...PipeArgs<T>]
: T extends readonly []
? [F<X, Y>]
: never
: never
type PipeReturn<Fns extends F<any, any>[]> =
Fns extends readonly [F<infer I, infer O>, ...infer T]
? T extends readonly [F<any, any>, ...any]
? F<I, Return<Last<T>>>
: F<I, O>
: never
在我展示我尝试过但不起作用的pipe 的签名之前,我先展示一些测试/示例及其预期行为
declare const a: any
const ae_pass_1: number = a as AssertReturn<number, number, number>
const ae_pass_2: string = a as AssertReturn<number, number, string>
// Expected compile error:
// Type 'string' does not satisfy the constraint 'number'.
// V
const ae_pass_3: string = a as AssertReturn<number, string, string>
// Expected compile error:
// Type 'string' is not assignable to type 'number'.
// V
const ae_fail_returnType: number = a as AssertReturn<number, number, string>
declare const pr1: PipeReturn<[F<number, string>]>
const pr1_pass: F<number, string> = pr1
// Expected compile error:
// Type 'F<number, string>' is not assignable to type 'F<number, boolean>'.
// V
const pr1_fail: F<number, boolean> = pr1
declare const pr2: PipeReturn<[F<number, string>, F<string, boolean>]>
const pr2_pass: F<number, boolean> = pr2
// Expected compile error:
// Type 'F<number, boolean>' is not assignable to type 'F<number, string>'.
// V
const pr2_fail: F<number, string> = pr2
declare const pa1: PipeArgs<[F<number, string>]>
const pa1_pass: [F<number, string>] = pa1
// Expected compile error:
// Type '[F<number, string>]' is not assignable to type '[F<number, boolean>]'.
// V
const pa1_fail: [F<number, boolean>] = pa1
declare const pa2: PipeArgs<[F<number, string>, F<string, boolean>]>
const pa2_pass: [F<number, string>, F<string, boolean>] = pa2
// Expected compile error:
// Type '[F<number, string>, F<string, boolean>]' is not assignable to type '[F<number, string>, F<number, boolean>]'.
// V
const pa2_fail: [F<number, string>, F<number, boolean>] = pa2
declare const numberToString: F<number, string>
declare const stringToBoolean: F<string, boolean>
// no compile error expected
const pipe_pass: F<number, boolean> =
pipe<[F<number, string>, F<string, boolean>]>(numberToString, stringToBoolean)
// no compile error expected
const pipe_pass_argTypeInfered: F<number, boolean> =
pipe(numberToString, stringToBoolean)
// assignment should cause compile error since second function should expect
// string as parameter, but actually expects number:
// Type 'F<number, boolean>' is not assignable to type 'F<number, string>'.
// V
const pipe_fail_returnType: F<number, string> =
pipe(numberToString, stringToBoolean)
// pipe call should cause compile error since second function should expect
// string as parameter, but actually expects number
// Expected compile error should be something like:
// Type 'F<number, string>' is not assignable to type 'F<string, T>'.
// V
const pipe_fail_args: F<number, string> = pipe(numberToString, numberToString)
以下是不同的pipe 签名以及失败的测试/示例(不符合预期)
function pipe<Fns extends F<any, any>[]>(...fns: PipeArgs<Fns>): PipeReturn<Fns>
const pipe_pass_argTypeInfered: F<number, boolean> =
// but
// Argument of type 'F<number, string>' is not assignable to parameter of type 'never'.(2345)
// The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
// V
pipe(numberToString, stringToBoolean)
与以前的方法相比,添加Fns &
function pipe<Fns extends F<any, any>[]>(...fns: Fns & PipeArgs<Fns>): PipeReturn<Fns>
修复了之前的错误,但不会导致这个预期的错误
// pipe call should cause compile error since second function should expect
// string as parameter, but actually expects number
// Expected compile error should be something like:
// Type 'F<number, string>' is not assignable to type 'F<string, T>'.
// V
const pipe_fail_args: F<number, string> = pipe(numberToString, numberToString)
另一种想法是在返回类型中断言Fns具有预期的结构,但是这个定义本身就有错误
// Type 'Fns' does not satisfy the constraint 'PipeArgs<Fns>'.
// Type 'F<any, any>[]' is not assignable to type 'PipeArgs<Fns>'.
// V
function pipe<Fns extends F<any, any>[]>(...fns: Fns): AssertReturn<PipeArgs<Fns>, Fns, PipeReturn<Fns>>
编辑 2: 顺便说一句,库 ts-toolbelt 有 several type definitions 可以键入您的 pipe 函数,最多 10 个参数(不是任意数量的参数)。
【问题讨论】:
-
你得到你想要的了吗?我成功地获得了管道最后一步的 returnType,但很难将前一个函数的 returnType 传递给下一个函数。
-
@captain-yossarian 你认为
pipe的实现方式与compose的实现方式相似吗(即参数顺序相反)? -
@Bonlou 不,根据 Anders Hejlsberg(TypeScript 的首席架构师)的this 评论,在类型推断中不引入新概念是不可能的。
-
是的,我认为这是可能的。 Compose 函数只是管道的反转,不是吗?
标签: typescript