-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Has anyone here considered using recursive/conditional/inferred types to allow typescript to statically type the pipe function in a truly variadic way?
Here's an example of what's possible, applied to simple function composition, if only requiring some casts to achieve, but in exchange allowing for infinitely large truly variadic pipe:
type PipeReturn<Rest extends ((x: any) => any)[]> =
Rest extends [infer F1 extends (x: any) => any,infer F2 extends (x: any) => any, ...infer RestTail extends ((x: any) => any)[]] ?
ReturnType<F1> extends Parameters<F2>[0] ?
PipeReturn<[(x: Parameters<F1>[0]) => ReturnType<F2>,...RestTail]> :
never :
Rest extends [(x: infer A) => infer B] ?
(x: A) => B :
Rest extends [] ?
<T>(x: T) => T :
((x: any) => any)
;
function pipe<F extends ((x: any) => any)[]>(...fs: F): PipeReturn<F> {
if(fs.length === 0) return (<T>(x: T) => x) as PipeReturn<F>;
if(fs.length === 1) return fs[0] as PipeReturn<F>;
const [f,...rest] = fs;
return ((x: any) => f(pipe(...rest))) as PipeReturn<F>;
}
This typechecks in typescript 5.4.5
To clarify my point, despite using any everywhere in the type definition, this makes the pipe function be truly variadic, using type recursion. Ensuring type safety between the return type of the previous function and parameter of the nextfunction, using conditional and inferred types. The case where the length of the function tuple is not known is covered by a default (x: any) => any in the last branch, but this could be changed to anything.
The only real downside I can see to this is probably my poorly-performing pipe function implementation, which can be changed to the existing definition if needed, and lacking support for older typescript versions that don't have these features.
I'm sure a similar approach could be used for Operators, as defined in this library.
I can start work on it if the author deems it useful enough.