The example of the release notes of TypeScript 4 shows how to use variadic tuple types to avoid several overload definitions. I guess it should be possible to type this pipe function for an arbitrary amount of arguments
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)
}
Run Code Online (Sandbox Code Playgroud)
A basic start could be
function pipe<Fns>(...fns: PipeArgs<Fns>): PipeReturn<Fns>
function pipe(...fns) {
return x => fns.reduce((res, f) => f(res), x)
}
Run Code Online (Sandbox Code Playgroud)
where the definitions of helper types PipeArgs<Fns> and PipeReturn<Fns> are yet missing. How can they be defined or is there another approach?
Edit: I'm not so confident anymore that it can be done, yet (TypeScript 4.1.2). The main issue are the rest parameters. The (tuple) type of the rest parameters fns of pipe has to be inferred, but a specific (circular?) structure has to be ensured. Here is my current approach (with a working 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
Run Code Online (Sandbox Code Playgroud)
Before I show the signatures of pipe that I tried, but are not working, I show some tests/examples and their expected behaviour
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)
Run Code Online (Sandbox Code Playgroud)
In the following the different pipe signatures and what test/example fails (is not as expected)
function pipe<Fns extends F<any, any>[]>(...fns: PipeArgs<Fns>): PipeReturn<Fns>
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
Add Fns & compared to previous approach
function pipe<Fns extends F<any, any>[]>(...fns: Fns & PipeArgs<Fns>): PipeReturn<Fns>
Run Code Online (Sandbox Code Playgroud)
fixes previous error, but does not cause this expected error
// 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)
Run Code Online (Sandbox Code Playgroud)
Another idea is to assert in the return type that Fns has the expected structure, but this definition has an error itself
// 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>>
Run Code Online (Sandbox Code Playgroud)
Edit 2: By the way, the library ts-toolbelt has several type definitions to type your pipe function up to 10 arguments (not an arbitrary amount of arguments).
看来,安德斯的评论已经过时了。
type Foo = typeof foo
type Bar = typeof bar
type Baz = typeof baz
type Fn = (a: any) => any
type Head<T extends any[]> = T extends [infer H, ...infer _] ? H : never
type Last<T extends any[]> = T extends [infer _]
? never
: T extends [...infer _, infer Tl]
? Tl
: never
type Allowed<T extends Fn[], Cache extends Fn[] = []> = T extends []
? Cache
: T extends [infer Lst]
? Lst extends Fn
? Allowed<[], [...Cache, Lst]>
: never
: T extends [infer Fst, ...infer Lst]
? Fst extends Fn
? Lst extends Fn[]
? Head<Lst> extends Fn
? ReturnType<Fst> extends Head<Parameters<Head<Lst>>>
? Allowed<Lst, [...Cache, Fst]>
: never
: never
: never
: never
: never
type FirstParameterOf<T extends Fn[]> = Head<T> extends Fn
? Head<Parameters<Head<T>>>
: never
type Return<T extends Fn[]> = Last<T> extends Fn ? ReturnType<Last<T>> : never
function pipe<
T extends Fn,
Fns extends T[],
Allow extends {
0: [never]
1: [FirstParameterOf<Fns>]
}[Allowed<Fns> extends never ? 0 : 1]
>(...args: [...Fns]): (...data: Allow) => Return<Fns>
function pipe<T extends Fn, Fns extends T[], Allow extends unknown[]>(
...args: [...Fns]
) {
return (...data: Allow) => args.reduce((acc, elem) => elem(acc), data)
}
const foo = (arg: string) => [1, 2, 3]
const baz = (arg: number[]) => 42
const bar = (arg: number) => ['str']
const check = pipe(foo, baz, bar)('hello') // string[]
const check3 = pipe(baz, bar)([2]) // string[]
const check2 = pipe(baz, bar)('hello') // expected error
Run Code Online (Sandbox Code Playgroud)
还有一个不错的 fnts库,它使用Compose具有更好的错误处理能力的类型
| 归档时间: |
|
| 查看次数: |
327 次 |
| 最近记录: |