Have ideas to improve npm?Join in the discussion! »

    TypeScript icon, indicating that this package has built-in type declarations

    3.1.1 • Public • Published

    Type-safe function composition for Typescript

    A micro-library for functional composition

    In the absence of |> (the pipe operator) it's useful to have a type-safe pipe function that can compose an a large (up to 64) number of unary functions. This minimal library contains a few different helper functions for this purpose.

    npm version Build Status


    Versions <=2.x erroneously used the term compose for left-to-right function composition. v3 is a major overhaul of this library and contains several breaking changes, both in the code, and in the meaning of compose.

    These are version >=3 documents. Please find v2.x documentation here


    This library makes use of leading/middle rest elements, introduced in Typescript version 4.2


    Suppose we have the following unary functions:

    const dinosaurify = (name:string) => `${name}-o-saurus`
    const sayHello = (name:string) => `Hello, ${name}!`

    We can compose these functions into a single function using the compose function:

    const sayHelloToDinosaur = compose(sayHello, dinosaurify)

    and call it

    sayHelloToDinosaur("mike") // "Hello, mike-o-saurus!"

    Note that with compose, function composition occurs from right-to-left.

    The pipe function composes its parameters from left-to-right, so the equivalent pipe version of the code above would be:

    const sayHelloToDinosaur_withPipe = pipe(dinosaurify, sayHello)

    The applyArgs helper

    Alternatively, we could have called the applyArgs helper, which is useful for ensuring that type inference flows inutitively through the composed functions. This makes more sense later when we start using it with (apparently) untyped arrow functions.

    applyArgs("mike").to(pipe(dinosaurify, sayHello)) // "Hello, mike-o-saurus!"

    or, less verbosely:

    applyArgs("mike")(pipe(dinosaurify, sayHello)) // "Hello, mike-o-saurus!"

    pipeInto function

    This is shorthand to combine the applyArgs helper with pipe, reducing the amount of boilerplate. Using pipeInto we can rewrite the above as:

    pipeInto("mike", dinosaurify, sayHello)

    In depth

    Pipes work with unary-functions, using the return value of one function as the only parameter to the next function.

    Defining higher-order unary map and filter functions

    Say we create our own versions the Array map and filter functions to work over Iterable<T>

    // helper function for making iterables from generator functions
    const toIterable = <T, TF extends () => IterableIterator<T>>(f: TF) => ({
      [Symbol.iterator]: f
    const _map = <T, TOut>(src: Iterable<T>, selector: (v: T, i: number) => TOut): Iterable<TOut> =>
      toIterable(function*() {
        let c = 0
        for (const v of src) {
          yield selector(v, c++)
    const _filter = <T>(src: Iterable<T>, pred: (v: T, i: number) => boolean): Iterable<T> =>
      toIterable(function*() {
        let i = 0
        for (const x of src) {
          if (pred(x, i++)) {
            yield x

    Here, the _map and _filter are not unary functions so cannot be used in a pipe/compose.

    Convert functions to unary with deferP0

    We can use the provided deferP0 method to transform these functions into functions that return a unary function (that takes a single parameter that was the first parameter of the original source function)

    So it turns functions of the form

    (src: TSrc, b: B, c: C, d: D) => R 

    into functions of the form

    (b: B, c: C, d: D) => (src: TSrc) => R

    Functions that return unary functions

    So, to make a composable map function:

    const map = deferP0(_map)

    Here, we transform the _map function with type

    <T, TOut>(src: Iterable<T>, selector: (v: T, i: number) => TOut): Iterable<TOut> 

    into the generated map function which has the type

    <T, TOut>(selector: (v: T, i: number) => TOut) => (src: Iterable<T>): Iterable<TOut>

    As can be seen, we end up with a function that generates a unary function.

    We can do the same with _filter

    const filter = deferP0(_filter)

    Now the map and filter functions that we generated above return unary functions and can be used in a pipe/compose with type inference "flowing" through the composed functions.

    Composing map and filter with pipe

    Let's use them with the pipe and the applyArgs helper (so that type information propagates through all the function parameters)

    const transformed = 
      applyArgs([1, 2, 3]).to(
          filter(x => x % 2 === 1),  // x is inferred as number
          map(x => x * 2)            // x is inferred as number
      ) // iterable with values [2, 6]

    When using "untyped" arrow functions, as above, by using the applyArgs helper, we can see how types are propagated through the functions without needing to provide types for any function parameters. However, we might just want a re-useable function composed of multiple functions, so we can use compose(...unaryFuncs) or pipe(...unaryFuncs) on their own... but we'll need to supply type-information, usually in just one place, so that typescript can infer other types successfully:

    const oddNumbersMultipliedByTwo =
        // pipe is inferred as (src:Iterable<number>)=>Iterable<string>
          // typescript can infer all other types when 
          // we provide this input type annotation (number)
          filter(x:number => x % 2 === 1), 
          map(x => x.toString()),   // x is inferred as number
          map(x => x + " " + x)     // x is inferred as string

    So oddNumbersMultipliedByTwoPipe has the inferred type

    (src: Iterable<number>) => Iterable<string>

    and when we use it...

    const r = oddMultipliedByTwo([1, 2, 3]) 
    // arr has type string[]
    const arr = [...r] // ["1 1", "2 2"]

    arr has type string[]


    Created using the wonderful https://github.com/alexjoverm/typescript-library-starter.


    npm i ts-functional-pipe

    DownloadsWeekly Downloads






    Unpacked Size

    774 kB

    Total Files


    Last publish


    • avatar