Skip to content

Instantly share code, notes, and snippets.

@PedroBern
Last active March 8, 2022 00:22
Show Gist options
  • Save PedroBern/bfe5113e43c2c44069af2a9428352704 to your computer and use it in GitHub Desktop.
Save PedroBern/bfe5113e43c2c44069af2a9428352704 to your computer and use it in GitHub Desktop.
Typed pipe
// inspired by
// https://stackoverflow.com/questions/53173203/typescript-recursive-function-composition
type Lookup<T, K extends keyof any, Else = never> = K extends keyof T
? T[K]
: Else
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never
type Func1 = (arg: any) => any
type ArgType<F, Else = never> = F extends (arg: infer A) => any ? A : Else
type AsChain<F extends [Func1, ...Func1[]], G extends Func1[] = Tail<F>> = {
[K in keyof F]: (arg: ArgType<F[K]>) => ArgType<Lookup<G, K, any>, any>
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Last<T extends any[]> = T extends [...infer F, infer L] ? L : never
type LaxReturnType<F> = F extends (...args: any) => infer R ? R : never
/**
* @example
* const playWithNumber = pipe(
* (n: number) => [n],
* (a: number[]) => `${a[0]}`,
* (s: string) => Number(s)
* )
* playWithNumber(1) // 1
*
* @param {Function[]} fns - list of functions where each function accepts
* arguments of the same type as the previous function return type.
* @returns {Function} a function that applies each function in the list, from
* left to right, with the result of the previous function.
*/
export function pipe<F extends [(arg: any) => any, ...((arg: any) => any)[]]>(
...fns: F & AsChain<F>
): (arg: ArgType<F[0]>) => LaxReturnType<Last<F>> {
return fns.reduce((prevFn, nextFn) => (value) => nextFn(prevFn(value)))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment