Skip to content

Instantly share code, notes, and snippets.

@nth-commit
Last active September 29, 2022 21:47
Show Gist options
  • Save nth-commit/29598c4d22bd16c5ce5fedd56dba1417 to your computer and use it in GitHub Desktop.
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)
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