03-javascriptTermsLevel_09Functional Programming & Composition

Functional Programming & Composition

Level 9 — Advanced Concepts & Patterns Composing pure functions; compose/pipe.


1. Prerequisites


2. Term Category

Language Core (Universal: Works everywhere): Functional Programming & Composition is a fundamental concept in this technology stack. Level 9 — Advanced Concepts & Patterns


3. Explanation

(1) Design Motivation — "Why did we design this?"

In Functional Programming (FP), we construct software applications by combining small, single-purpose, pure functions. Rather than writing massive class structures, we create simple functions that take data, transform it, and return a new result.

However, nesting multiple function calls together makes code extremely hard to read:

const result = slugify(lowercase(trim(userInput))); // Nested from right-to-left!

To solve this and create clean data pipelines, we use Function Composition:

  • Composition merges multiple functions into a single unified function. When data is passed to the merged function, it flows through each underlying function in sequence.
  • compose(...fns): Combines functions to run from right-to-left (matching the mathematical nesting order: f(g(x))f(g(x))).
  • pipe(...fns): Combines functions to run from left-to-right (matching standard reading order and logical data streams: xfgx \to f \to g).

In JavaScript, both helpers are commonly implemented using Array.prototype.reduce() and reduceRight().

(2) Reality Metaphor

Imagine a manufacturing factory producing wooden toys.

  • Nested Functions are like putting a block of raw wood inside a box, placing that box inside a carving crate, and placing the crate inside a paint drum. You have a nested, confusing structure where it is difficult to inspect the intermediate steps.
  • Function Composition (pipe) is a clean, sequential conveyor belt assembly line:
    • Station 1: Sand the wood (sand).
    • Station 2: Carve the shape (carve).
    • Station 3: Paint it blue (paint).
  • You construct this assembly line: const createToy = pipe(sand, carve, paint);. When you drop raw wood onto the conveyor belt (createToy(wood)), it flows from left-to-right through each station, outputting a finished toy.

(3) JavaScript Code Examples

Implementing compose and pipe Helpers

// compose: executes from right-to-left
const compose = (...fns) => (initialVal) => 
  fns.reduceRight((acc, fn) => fn(acc), initialVal);

// pipe: executes from left-to-right (recommended for readability)
const pipe = (...fns) => (initialVal) => 
  fns.reduce((acc, fn) => fn(acc), initialVal);

// Simple math operations
const add5 = x => x + 5;
const double = x => x * 2;
const square = x => x * x;

// 1. Using pipe: (x + 5) -> (* 2) -> (squared)
const pipePipeline = pipe(add5, double, square);
console.log(pipePipeline(5)); // ((5+5) * 2)^2 = (20)^2 = 400

// 2. Using compose: (squared) <- (* 2) <- (x + 5)
const composePipeline = compose(square, double, add5);
console.log(composePipeline(5)); // 400

Practical Text Normalization Pipeline

const trim = str => str.trim();
const lowercase = str => str.toLowerCase();
const slugify = str => str.replace(/\s+/g, "-");

const normalizeTitle = pipe(trim, lowercase, slugify);

const rawTitle = "  My First JavaScript Term  ";
console.log(normalizeTitle(rawTitle)); // "my-first-javascript-term"

4. Common Mistakes & Pitfalls

Mistake 1: Confusing the Execution Order of compose and pipe

The mistake: Writing a pipeline using compose expecting it to run in the order the arguments are written.

Why it's wrong: compose runs functions from right-to-left. If your functions depend on the output of previous steps, running them in reverse order will crash or return incorrect results.

Incorrect:

const trimAndLength = compose(trim, (str) => str.length); // Error!
// Evaluates: trim(str.length) -> tries to trim a number!

Fix:

// Use pipe for left-to-right order:
const trimAndLength = pipe(trim, (str) => str.length); 

// Or reverse the order inside compose:
const trimAndLength2 = compose((str) => str.length, trim);

Mistake 2: Losing Context Binding (this) in Functional Programming Callbacks

The mistake: Passing methods from Functional Programming instances as standalone callbacks to timers or event listeners without explicitly binding this.

Why it's wrong: Extracting object methods disassociates them from their target parent instance, causing this to resolve to undefined (in strict mode) or window/globalThis at runtime.

Incorrect:

const obj = {
    name: "functional_programming",
    log() { console.log(this.name); }
};
setTimeout(obj.log, 100); // ❌ Output: undefined (loses object context)

Fix:

const obj = {
    name: "functional_programming",
    log() { console.log(this.name); }
};
setTimeout(() => obj.log(), 100); // Correct: Arrow function captures lexical context

Mistake 3: Unhandled Asynchronous Failures in Functional Programming Operations

The mistake: Executing asynchronous operations within Functional Programming without wrapping await calls in try...catch blocks or chaining .catch().

Why it's wrong: Unhandled promise rejections trigger UnhandledPromiseRejectionWarning in Node.js or unhandled rejection errors in modern browsers, leaving application state in corrupted or uncoordinated states.

Incorrect:

async function processData() {
    const res = await fetch("/api/functional_programming"); // ❌ Unhandled network failure crashes execution flow
    const data = await res.json();
    return data;
}

Fix:

async function processData() {
    try {
        const res = await fetch("/api/functional_programming");
        if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
        return await res.json();
    } catch (err) {
        console.error(`Caught error in functional_programming: ${err.message}`);
        return null;
    }
}

5. Practice Exercises

Exercise 1: Functional Function Composition Pipeline (compose)

Scenario: A data processing library builds a right-to-left function composition utility (compose) to chain pure data transformations cleanly.

Requirements:

  1. Write compose(…fns).
  2. Return function taking initial value.
  3. Execute functions right-to-left via Array.prototype.reduceRight().
Answer

Implementation

function compose(...fns) {
  return function(initialValue) {
    return fns.reduceRight((acc, fn) => fn(acc), initialValue);
  };
}

// Verification tests
const trim = s => s.trim();
const uppercase = s => s.toUpperCase();
const exclaim = s => `${s}!`;

const formatGreeting = compose(exclaim, uppercase, trim);

console.assert(formatGreeting("   hello world   ") === "HELLO WORLD!", "Test 1 Failed");

Technical Explanation

  1. Function Composition Concept: Combining two or more functions to produce a new function: (f ∘ g)(x) = f(g(x)).
  2. Right-to-Left Execution Order: Standard mathematical composition evaluates arguments right-to-left.
  3. Declarative Data Transformation: Eliminates intermediate temporary variables in multi-step data processing pipelines.

Exercise 2: Pure Function Data Pipe Utility (pipe)

Scenario: A analytics data engine builds a left-to-right pipe function (pipe) to transform transaction records without mutating raw inputs.

Requirements:

  1. Write pipe(…fns).
  2. Execute functions left-to-right via Array.prototype.reduce().
  3. Ensure input objects remain immutable.
Answer

Implementation

function pipe(...fns) {
  return function(initialValue) {
    return fns.reduce((acc, fn) => fn(acc), initialValue);
  };
}

// Verification tests
const filterActive = users => users.filter(u => u.active);
const extractNames = users => users.map(u => u.name);
const sortNames = names => [...names].sort();

const getActiveUserNames = pipe(filterActive, extractNames, sortNames);

const rawUsers = [
  { name: "Bob", active: true },
  { name: "Alice", active: true },
  { name: "Charlie", active: false }
];

const result = getActiveUserNames(rawUsers);
console.assert(result.join(",") === "Alice,Bob", "Test 1 Failed");
console.assert(rawUsers.length === 3, "Test 2 Failed: Pure pipe must not mutate raw inputs");

Technical Explanation

  1. Pipe vs Compose: Pipe executes functions left-to-right (reading order), whereas Compose executes right-to-left.
  2. Pure Function Principle: Pure functions produce identical output for identical input with ZERO side-effects.
  3. Immutable Data Flow: Pipelines work on shallow copies or new references to preserve raw dataset integrity.

Exercise 3: Higher-Order Function Map-Filter-Reduce Pipeline

Scenario: An e-commerce reporting tool aggregates total revenue from active orders using a chain of higher-order array functions.

Requirements:

  1. Write calculateActiveRevenue(orders).
  2. Filter orders with status === "COMPLETED".
  3. Map items to revenue (price * quantity).
  4. Sum total revenue using reduce().
Answer

Implementation

function calculateActiveRevenue(orders) {
  if (!Array.isArray(orders)) return 0;

  return orders
    .filter(order => order.status === "COMPLETED")
    .map(order => order.price * order.quantity)
    .reduce((total, revenue) => total + revenue, 0);
}

// Verification tests
const orders = [
  { id: 1, status: "COMPLETED", price: 100, quantity: 2 },
  { id: 2, status: "CANCELLED", price: 50, quantity: 1 },
  { id: 3, status: "COMPLETED", price: 30, quantity: 3 }
];

console.assert(calculateActiveRevenue(orders) === 290, "Test 1 Failed: 200 + 90 = 290");

Technical Explanation

  1. Higher-Order Functions (HOFs): Functions that accept other functions as arguments or return functions as outputs.
  2. Declarative Iteration: Replaces imperative for/while loops with expressive declarative data methods.
  3. Chainable Transformations: Array methods filter, map, and reduce chain together without mutating original arrays.


7. Key Takeaways

  • Function Composition combines multiple small, pure functions into a single pipeline.
  • compose() executes functions from right-to-left (inside-out).
  • pipe() executes functions from left-to-right (data flow).
  • Custom compose and pipe functions are written using reduce and reduceRight array helper methods.
  • Composition promotes highly reusable, modular, and testable code structures.
Built with LogoFlowershow