Skip to content
VersionSize

compose

The compose utility performs functional composition from right to left. It takes multiple functions and returns a single function that passes its result from one call to the previous one, following standard mathematical notation $f(g(x))$.

Source Code

View Source Code
ts
import type { Fn } from '../types';

import { assert } from './assert';

type LastParameters<T extends readonly Fn[]> = T extends [...unknown[], infer Last extends Fn]
  ? Parameters<Last>
  : never;
type FirstReturnType<T extends readonly Fn[]> = T extends [infer First extends Fn, ...unknown[]]
  ? ReturnType<First>
  : never;

/**
 * Composes multiple functions into a single function. It starts from the rightmost function and proceeds to the left.
 *
 * @example
 * ```ts
 * const add = (x) => x + 2;
 * const multiply = (x) => x * 3;
 * const subtract = (x) => x - 4;
 * const composedFn = compose(subtract, multiply, add);
 * composedFn(5); // ((5 + 2) * 3) - 4 = 17
 * ```
 *
 * @param fns - List of the functions to be composed.
 *
 * @returns A new function that is the composition of the input functions.
 */
export function compose<T extends readonly [Fn, ...Fn[]]>(
  ...fns: T
): (...args: LastParameters<T>) => FirstReturnType<T> {
  assert(fns.length > 0, 'compose requires at least one function', { args: { fns } });

  const lastFn = fns[fns.length - 1];
  const restFns = fns.slice(0, -1);

  return ((...args: LastParameters<T>) => restFns.reduceRight((prev, fn) => fn(prev), lastFn(...args))) as (
    ...args: LastParameters<T>
  ) => FirstReturnType<T>;
}

Features

  • Isomorphic: Works in both Browser and Node.js.
  • Async Support: Automatically handles Promises. If any function in the chain returns a Promise, the final result will be a Promise.
  • Type-safe: Properly infers input and output types through the entire chain.
  • Right-to-Left: Executes functions in reverse order of provided arguments.

API

ts
function compose<T extends any[], R>(
  ...fns: [(arg: any) => R, ...Array<(arg: any) => any>, (...args: T) => any]
): (...args: T) => R | Promise<R>;

Parameters

  • ...fns: A sequence of functions to be composed.

Returns

  • A new function that represents the composition.

Examples

Synchronous Composition

ts
import { compose } from '@vielzeug/toolkit';

const addTwo = (n: number) => n + 2;
const double = (n: number) => n * 2;

// result = addTwo(double(x))
const calculate = compose(addTwo, double);

calculate(5); // (5 * 2) + 2 = 12

Asynchronous Composition

ts
import { compose, sleep } from '@vielzeug/toolkit';

const saveToDb = async (data: string) => {
  await sleep(10);
  return { success: true, data };
};

const format = (s: string) => s.trim().toUpperCase();

const processAndSave = compose(saveToDb, format);

await processAndSave('  hello  '); // { success: true, data: 'HELLO' }

Implementation Notes

  • If only one function is provided, it is returned as-is.
  • Uses reduceRight internally to chain function calls.
  • Throws TypeError if any provided argument is not a function.

See Also

  • pipe: Functional composition from left to right.
  • curry: Transform a function into a sequence of unary functions.
  • configure: Preconfigure trailing arguments for unary composition.