A while ago a friendly stack overflow user helped me to solve to get a compose function to work properly with TypeScript generics.
type OuterFunction<OA, OR> = (arg: OA) => OR;
type InnerFunction<IA extends unknown[], IR> = (...args: IA) => IR;
const compose = <T extends unknown[], V, W>(
outer: OuterFunction<V, W>,
inner: InnerFunction<T, V>
) => (...innerParams: T) => outer(inner(...innerParams));
This works great. So the resulting composed function has the arguments type of inner function and the return type of outer function, and correctly expects outer functions arguments to match inner functions return type.
And here is my pipe function utilizing that same compose with reduceRight, allowing to define functions from more than two other functions:
const pipe = (...functions: any[]) =>
functions.reduceRight((prev, curr) => compose(prev, curr));
This runs and works JavaScript wise as expected. Types are, obviously, completely vanishing when done like this.
I was fiddling and doing more different ways than I can remember and it also doesn't really make sense to list them up here, because none of them worked at all. They all did not even compile, or only resulted in non types just as the basic version above.
Here this is in a TypeScript playground of this:
https://tsplay.dev/w62jEw
How does fp-ts do this?
I looked at how fp-ts does this with their pipe, and concluded that their solution will work very well, but is not as nice as using reduceRight (it is basically a huge overload cascade they do to achieve it).
Since piping > 18 functions together is actually not a scenario I have to expect as quite common, my fiddling for that solution is actually redundant.
But perhaps there is a nice solution out there, and in that case it might be interesting for even more than just myself to know about it ;)
TIA for any hints.