-
-
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()), | |
); |
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 :)
@am-a, yes! It can!
I originally had:
Which is an error.
I'll update, thanks!