-
-
Save nth-commit/29598c4d22bd16c5ce5fedd56dba1417 to your computer and use it in GitHub Desktop.
| import { OperatorFunction } from 'ix/interfaces'; | |
| import { pipe } from 'ix/iterable'; | |
| import { map } from 'ix/iterable/operators'; | |
| /** | |
| * Creates a new type which is the first element of a non-empty tuple type. | |
| * | |
| * @example type T = Head<[string, number, Object]>; // string | |
| */ | |
| export type Head<Ts extends [any, ...any[]]> = Ts extends [infer T, ...any[]] ? T : never; | |
| /** | |
| * Creates a new type which is the last element of a non-empty tuple type. | |
| * | |
| * @example type T = Bottom<[string, number, Object]>; // Object | |
| */ | |
| export type Bottom<Ts extends [any, ...any[]]> = Ts extends [...infer _, infer T] ? T : never; | |
| /** | |
| * Creates a type which is the original elements of a tuple type, paired with their right neighbour. It returns an | |
| * empty tuple type if the original has less than two elements (such is pairwise). This represents the essence of | |
| * function composition - the output of the last being passed into the input of the next. | |
| * | |
| * Not possible in < 4.1 - recursive condition type. | |
| * | |
| * @example type T = Pairwise<[]> // [] | |
| * @example type U = Pairwise<[string]> // [] | |
| * @example type V = Pairwise<[string, number]> // [[string, number]] | |
| * @example type W = Pairwise<[string, number, Object]> // [[string, number], [number, Object]] | |
| */ | |
| export type Pairwise<Ts extends any[]> = Ts extends [infer T, ...infer Us] | |
| ? Us extends [infer U, ...infer Vs] | |
| ? [[T, U], ...Pairwise<[U, ...Vs]>] | |
| : [] | |
| : []; | |
| /** | |
| * Creates a type which is the original elements of a tuple type, mapped into a function composition pattern using. | |
| * Similar to @see Pairwise, but the pair is mapped from a tuple of two elements into ixjs's OperatorFunction. | |
| */ | |
| export type OperatorFunctions<Ts extends any[]> = Pairwise<Ts> extends infer Pairs | |
| ? { [P in keyof Pairs]: Pairs[P] extends [infer U, infer T] ? OperatorFunction<U, T> : never } | |
| : []; | |
| export const variadicPipe = <Ts extends [any, ...any]>( | |
| source: Iterable<Head<Ts>>, | |
| ...ops: OperatorFunctions<Ts> extends [...infer U] ? U : [] // Ewww conditional, otherwise TS complains that we can't spread a non-array. | |
| ): Iterable<Bottom<Ts>> => (pipe as any)(source, ...ops); | |
| /** | |
| * Usage. | |
| * | |
| * The worse part is having to declare the type params up front, rather than having them inferred from the runtime | |
| * args, which sucks (there might be a better way to structure this). | |
| */ | |
| const source: Iterable<string> = ['0', '1', '2']; | |
| const result: Iterable<string> = variadicPipe<[string, number, string]>( | |
| source, | |
| map((x) => parseInt(x)), | |
| map((x) => x.toString()), | |
| ); |
@am-a, yes! It can!
I originally had:
export type Bottom<Ts extends [any, ...any[]]> = Ts extends [...any[], infer Us] ? Us : never;Which is an error.
I'll update, thanks!
The problem (that I also faced) is that as soon as you're going to use infer then your composition will start yielding unknown on generics. This was a big limitation for me, so I wrote a 10-overload pipe, compose in async and sync modes:
https://github.com/millsp/ts-toolbelt/blob/master/src/Function/Pipe/Multi/Sync.ts
If you're interested I also wrote a variadic version of Pipe - but just like yours, it will yield unknown if you chain generic functions:
https://github.com/millsp/ts-toolbelt/blob/master/src/Function/Pipe.ts
The goal of ts-toolbelt is to hide away all these type system related implementations, while providing a standard library for the type system:
https://github.com/millsp/ts-toolbelt
The project has a double purpose, I answer any type system related questions.
You guys are always welcome to ask for help if needed :)
Can't bottom just be?: