We use cookies to enhance your experience on the site
CodeWorlds

Function Composition: compose and pipe

In the genetic laboratory of Jurassic Park, dinosaur DNA goes through a series of processing stages - extraction, purification, sequencing, validation. Each stage is a separate operation, and chaining them together produces the complete process. This is exactly how function composition works - combining simple functions into more complex operations.

What is Function Composition?

Function composition involves combining two or more functions where the output of one becomes the input of the next. Mathematically:

f(g(x))
- first we apply
g
, then
f
.

1// Individual operations on dinosaur data
2const normalize = (name) => name.trim().toLowerCase();
3const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
4const addPrefix = (name) => 'DINO-' + name;
5
6// Without composition - nested calls (hard to read)
7const result = addPrefix(capitalize(normalize('  rex  ')));
8// 'DINO-Rex'
9
10// With composition - readable pipeline
11const formatDinoName = (name) => addPrefix(capitalize(normalize(name)));
12formatDinoName('  rex  '); // 'DINO-Rex'

The compose Function

compose
chains functions from right to left - the last function in the list is executed first. This mirrors the mathematical notation
f(g(x))
.

1function compose(...fns) {
2  return (value) => fns.reduceRight(
3    (acc, fn) => fn(acc),
4    value
5  );
6}
7
8// Functions processing a DNA sample
9const extractDNA = (sample) => ({ ...sample, dna: sample.tissue + '-DNA' });
10const sequenceDNA = (sample) => ({ ...sample, sequence: sample.dna + '-SEQ' });
11const validateDNA = (sample) => ({ ...sample, valid: sample.sequence.length > 5 });
12
13// Compose: right to left (extractDNA -> sequenceDNA -> validateDNA)
14const processSample = compose(validateDNA, sequenceDNA, extractDNA);
15
16const result = processSample({ tissue: 'TREX-001' });
17// { tissue: 'TREX-001', dna: 'TREX-001-DNA', sequence: 'TREX-001-DNA-SEQ', valid: true }

The pipe Function

pipe
is the inverse of
compose
- it chains functions from left to right. It reads like a natural data flow, which many programmers find more intuitive.

1function pipe(...fns) {
2  return (value) => fns.reduce(
3    (acc, fn) => fn(acc),
4    value
5  );
6}
7
8// Pipe: left to right (natural reading direction)
9const processSample = pipe(extractDNA, sequenceDNA, validateDNA);
10// Identical result, but more readable order
11
12// Practical example - processing park data
13const processReport = pipe(
14  (data) => data.filter((d) => d.status === 'active'),
15  (data) => data.map((d) => ({ name: d.name, zone: d.zone })),
16  (data) => data.sort((a, b) => a.name.localeCompare(b.name)),
17);
18
19const parkData = [
20  { name: 'Rex', zone: 'A', status: 'active' },
21  { name: 'Stego', zone: 'B', status: 'inactive' },
22  { name: 'Blue', zone: 'A', status: 'active' },
23];
24processReport(parkData);
25// [{ name: 'Blue', zone: 'A' }, { name: 'Rex', zone: 'A' }]

Compose vs Pipe - When to Use Which?

Both functions do the same thing, but in reverse order. The choice depends on preference:

1// compose: right to left (like in mathematics)
2const process1 = compose(validate, transform, parse);
3// Read as: validate(transform(parse(x)))
4
5// pipe: left to right (like a natural flow)
6const process2 = pipe(parse, transform, validate);
7// Read as: x -> parse -> transform -> validate

In practice,

pipe
is used more often because it mirrors the natural direction of data processing - from input to output, from top to bottom.

Go to CodeWorlds