Some time ago, I received valuable assistance from a helpful stack overflow user who guided me on using TypeScript generics to make my compose function work effectively. Here's the code snippet that was shared:
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 solution has been working wonderfully for me. The resulting composed function takes on the argument type of the inner function and the return type of the outer function, ensuring that the outer function's arguments match the inner functions return type.
I also created a pipe function utilizing the same compose function with reduceRight, allowing for defining functions from more than two other functions:
const pipe = (...functions: any[]) =>
functions.reduceRight((prev, curr) => compose(prev, curr));
While this setup works perfectly in JavaScript, the types are lost in the process, which is expected.
I experimented with various approaches beyond what I can recall, but none of them proved successful. They either failed to compile or resulted in missing types like the basic version above.
You can view this in action on TypeScript playground:
https://tsplay.dev/w62jEw
Curious about fp-ts implementation
In exploring how fp-ts manages this with their pipe function, it became evident that while their approach works well, it's not as elegant as using reduceRight (involving numerous cascading overloads).
Given that chaining more than 18 functions together isn't a common scenario, my pursuit of that specific solution may be unnecessary. However, discovering an alternative could be beneficial for others as well ;)
Thanks in advance for any insights!