Last active
September 29, 2022 21:47
-
-
Save nth-commit/29598c4d22bd16c5ce5fedd56dba1417 to your computer and use it in GitHub Desktop.
Function composition in TypeScript 4.1 (on top of ixjs's pipe)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()), | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://millsp.github.io/ts-toolbelt/modules/_function_pipe_.html