TypeScript Version: 3.4.1
Search Terms: generic rest parameters pipe compose
Code
I'm defining pipe using generic rest parameters as recommended here: https://github.com/Microsoft/TypeScript/issues/29904#issuecomment-471334674.
// Copied from https://github.com/Microsoft/TypeScript/issues/29904#issuecomment-471334674
declare function pipe<A extends any[], B>(ab: (...args: A) => B): (...args: A) => B;
declare function pipe<A extends any[], B, C>(
ab: (...args: A) => B,
bc: (b: B) => C,
): (...args: A) => C;
declare function pipe<A extends any[], B, C, D>(
ab: (...args: A) => B,
bc: (b: B) => C,
cd: (c: C) => D,
): (...args: A) => D;
declare const myGenericFn: <T>(t: T) => string[];
declare const join: (strings: string[]) => string;
// Expected type: `<T>(t: T) => string`
// Actual type: `(t: any) => string`
const fn1 = pipe(
myGenericFn,
join,
);
// Workaround:
// Expected and actual type: `<T>(t: T) => string`
const fn2 = pipe(
myGenericFn,
strings => join(strings),
);
Playground Link: https://www.typescriptlang.org/play/index.html#src=declare%20function%20pipe%3CA%20extends%20any%5B%5D%2C%20B%3E(ab%3A%20(...args%3A%20A)%20%3D%3E%20B)%3A%20(...args%3A%20A)%20%3D%3E%20B%3B%0D%0Adeclare%20function%20pipe%3CA%20extends%20any%5B%5D%2C%20B%2C%20C%3E(%0D%0A%20%20%20%20ab%3A%20(...args%3A%20A)%20%3D%3E%20B%2C%0D%0A%20%20%20%20bc%3A%20(b%3A%20B)%20%3D%3E%20C%2C%0D%0A)%3A%20(...args%3A%20A)%20%3D%3E%20C%3B%0D%0Adeclare%20function%20pipe%3CA%20extends%20any%5B%5D%2C%20B%2C%20C%2C%20D%3E(%0D%0A%20%20%20%20ab%3A%20(...args%3A%20A)%20%3D%3E%20B%2C%0D%0A%20%20%20%20bc%3A%20(b%3A%20B)%20%3D%3E%20C%2C%0D%0A%20%20%20%20cd%3A%20(c%3A%20C)%20%3D%3E%20D%2C%0D%0A)%3A%20(...args%3A%20A)%20%3D%3E%20D%3B%0D%0A%0D%0Adeclare%20const%20myGenericFn%3A%20%3CT%3E(t%3A%20T)%20%3D%3E%20string%5B%5D%3B%0D%0Adeclare%20const%20join%3A%20(strings%3A%20string%5B%5D)%20%3D%3E%20string%3B%0D%0A%0D%0A%2F%2F%20Expected%20type%3A%20%60%3CT%3E(t%3A%20T)%20%3D%3E%20string%60%0D%0A%2F%2F%20Actual%20type%3A%20%60(t%3A%20any)%20%3D%3E%20string%60%0D%0Aconst%20fn1%20%3D%20pipe(%0D%0A%20%20%20%20myGenericFn%2C%0D%0A%20%20%20%20join%2C%0D%0A)%3B%0D%0A%0D%0A%2F%2F%20Workaround%3A%0D%0A%2F%2F%20Expected%20and%20actual%20type%3A%20%60%3CT%3E(t%3A%20T)%20%3D%3E%20string%60%0D%0Aconst%20fn2%20%3D%20pipe(%0D%0A%20%20%20%20myGenericFn%2C%0D%0A%20%20%20%20strings%20%3D%3E%20join(strings)%2C%0D%0A)%3B%0D%0A
Related Issues: https://github.com/Microsoft/TypeScript/issues/29904
/cc @ahejlsberg
Looks like the type is being pulled from the constraint on pipe's type parameter - use unknown[] instead of any[] and you get unknown as the parameter type instead.
Is that case, my next question is why does it not propagate the generic?
Here's how resolution of the call proceeds:
myGenericFn) is deferred because it is a generic function.B and C.A or B. Therefore, T isn't promoted, but rather myGenericFn is instantiated using the existing inferences--and since there are no inference candidates for A you get the default constraint any.It's not immediately clear how we can do better here.
but rather myGenericFn is instantiated using the existing inferences--and since there are no inference candidates for A you get the default constraint any.
What if we only produce inferences for the inferred part of the context, akin to the cloneInferredPartOfContext call we do for return type inference? (And then promote the parameters we don't have inferences for)
Hey guys, for what its worth, I've come up with a recursive Pipe and Compose which preserves parameter names. I think this is a better experience than have variables names like a, b etc.
It tolerates generics better but this too has issues with generics.
Anyway, I just published this library
Type only:
https://github.com/babakness/pipe-and-compose-types
Implementation:
https://github.com/babakness/pipe-and-compose
and here is an article I wrote on it
https://dev.to/babak/introducing-the-recursive-pipe-and-compose-types-3g9o
Update.
I believe the key issue with generics in the recursive version has to do with infer. These are the two main helpers that Pipe from
https://github.com/babakness/pipe-and-compose-types
Relies on
/**
* Extracts function arguments
*/
export type ExtractFunctionArguments < Fn > = Fn extends ( ...args: infer P ) => any ? P : never
/**
* Extracts function return values
*/
export type ExtractFunctionReturnValue<Fn> = Fn extends ( ...args: any[] ) => infer P ? P : never
Example
type Foo = ExtractFunctionArguments< <A>(a:A, b:number) => A >
// Foo has type [ {} , number ]
In researching this SO question I landed here, and I'm not sure if this is the same issue or a different one:
declare function pipe<A extends any[], B, C>(
ab: (...args: A) => B,
bc: (b: B) => C,
): (...args: A) => C;
declare function list<T>(a: T): T[];
declare function acceptNumArray(x: number[]): void
const f = pipe(list, acceptNumArray);
// inferred as pipe<[any], number[], void>(list, acceptNumArray)
// why is A inferred as [any] and not [number]?
In the above, B and C are correctly inferred, but A is not. Does the reasoning in this comment apply here too? It seems reasonable that:
Processing of the first argument (list) is deferred because it is a generic function.
Processing of the second argument produces inference candidates for B and C.
Processing of the first argument notices that inference candidates exist for A or B. Therefore, T isn't promoted, but rather list is instantiated using the existing inferences
But then the rest of it, "and since there are no inference candidates for A you get the default constraint any" doesn't sound right. It seems that if list is instantiated so that its return type has to match the inferred B type, then there's an inference candidate for A. Why does this one fail?
Thanks!
Does the reasoning in this comment apply here too?
Yes, that reasoning applies to this example as well.
Most helpful comment
Hey guys, for what its worth, I've come up with a recursive Pipe and Compose which preserves parameter names. I think this is a better experience than have variables names like
a,betc.It tolerates generics better but this too has issues with generics.
Anyway, I just published this library
Type only:
https://github.com/babakness/pipe-and-compose-types
Implementation:
https://github.com/babakness/pipe-and-compose
and here is an article I wrote on it
https://dev.to/babak/introducing-the-recursive-pipe-and-compose-types-3g9o