Function composition means building one function out of several small ones, where the result of each becomes the input of the next. Written as nested calls, f(g(x)) runs g on x, then f on what g returned. Each small function does one job, can be tested on its own, and can be reused in other combinations. It works like an assembly line: every station does one thing to the item and passes it on.
Here's a title turned into a URL slug in three steps, written as nested calls:
const trim = (text) => text.trim();
const lower = (text) => text.toLowerCase();
const hyphenate = (text) => text.replaceAll(" ", "-");
const title = " Hello World ";
trim(title); // "Hello World"
lower(trim(title)); // "hello world"
hyphenate(lower(trim(title))); // "hello-world"
Nested calls read inside out: trim runs first but is written last, and with five or six steps the brackets get hard to follow. So libraries offer helpers that take the functions as a list and return the combined function. compose takes them in the same order as the nested calls and runs them right to left, the way composition is written in maths. pipe takes them in the order they run, left to right, the way you read:
| Written as | The functions are listed | They run | Result |
|---|---|---|---|
hyphenate(lower(trim(title))) | Outermost first | trim, lower, hyphenate | "hello-world" |
compose(hyphenate, lower, trim)(title) | Right to left: the last runs first | trim, lower, hyphenate | "hello-world" |
pipe(trim, lower, hyphenate)(title) | Left to right: the first runs first | trim, lower, hyphenate | "hello-world" |
The first step gets whatever the combined function is called with. A function returns one value, so every step after it gets a single argument: the previous step's result. Steps can also be async. When a step in an async pipe returns a promise, the pipe waits for it and hands the value it resolves to on to the next step, so the whole pipeline returns a promise. If a step fails, the steps after it don't run.
You meet composition in Lodash's _.flow (a pipe) and _.flowRight (a compose), in Redux's compose, used to combine store enhancers, and in RxJS, where observable.pipe(map(...), filter(...)) runs operators left to right.
Write compose(...fns). It returns one function that runs fns from right to left: compose(f, g, h)(x) is f(g(h(x))).
The rightmost function gets all the arguments and the caller's this; every other function gets the previous result as its only argument.
With no functions, compose() returns a function that returns its first argument. If any argument isn't a function, compose throws a TypeError right away, before anything is called. The composed function can be called any number of times.
const shout = compose((s) => s + "!", (s) => s.toUpperCase());
shout("hi"); // "HI!"
const doubleSum = compose((n) => n * 2, (a, b) => a + b);
doubleSum(2, 3); // 10
expect(compose((x) => x + 1, (x) => x * 2)(3)).toBe(7);
expect(compose((s) => s + "a", (s) => s + "b", (s) => s + "c")("")).toBe("cba");
expect(compose((x) => x * 10, (a, b) => a - b)(5, 2)).toBe(30);
const outer = spy((x) => x);
compose(outer, (a, b) => a + b)(1, 2);
expect(outer.calls[0].args).toEqual([3]);
const obj = {
n: 4,
next: compose(
(x) => x + 1,
function () {
return this.n;
},
),
};
expect(obj.next()).toBe(5);
const value = { id: 1 };
expect(compose()(value)).toBe(value);
expect(compose()(5, 6)).toBe(5);
expect(compose((a, b) => a * b)(3, 4)).toBe(12);
const addBA = compose((s) => s + "a", (s) => s + "b");
expect(addBA("")).toBe("ba");
expect(addBA("")).toBe("ba");
expect(addBA("x")).toBe("xba");
expect(() => compose((x) => x, 42)).toThrow(TypeError);
expect(() => compose("upper")).toThrow(TypeError);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Compose. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.