Functional Programming
02 / 02

Currying, Closures & Handling Failure Functionally

Functional Programming: Currying, Closures & Handling Failure

Currying, Partial Application & Closures

// Currying: transform a multi-arg function into a chain of
// single-argument functions
const add = a => b => a + b;
const add5 = add(5);   // a specialized function, remembers a=5 via closure
add5(3); // 8

// The CLOSURE is what makes this work -- the returned inner function
// retains access to 'a' even after the outer add() call has finished
function multiplier(n) {
  return function (x) {
    return x * n; // 'n' is closed over
  };
}
const triple = multiplier(3);
triple(7); // 21

// Partial application: fix SOME arguments upfront, any number remain
// (distinct from currying's strict one-argument-per-call chain)
function add3(a, b, c) { return a + b + c; }
const addTo5 = add3.bind(null, 5);
addTo5(2, 3); // 10 -- two args at once, not curried one-at-a-time

Recursion Instead of Mutable Loops

// Iteration expressed through recursive calls, no mutating
// loop counter/accumulator variable
function sum([head, ...tail]) {
  if (head === undefined) return 0;
  return head + sum(tail);
}
sum([1, 2, 3, 4]); // 10

function factorial(n, acc = 1) {
  return n <= 1 ? acc : factorial(n - 1, n * acc); // tail-call form

Result/Either: Handling Failure Without Exceptions

// A thrown exception is an invisible control-flow path not
// reflected in a function's signature -- easy to forget to catch.
// A Result type makes "this can fail" part of the type contract.
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

function parseAge(input: string): Result<number, string> {
  const n = Number(input);
  if (Number.isNaN(n) || n < 0) {
    return { ok: false, error: `Invalid age: ${input}` };
  }
  return { ok: true, value: n };
}

const result = parseAge('30');
if (result.ok) {
  console.log(result.value); // caller MUST check .ok before accessing .value
} else {
  console.error(result.error);
}

// This is the same shape as a Promise (which resolves or rejects)
// or Maybe/Option (present or absent) -- 'monad-like' wrappers that
// provide a consistent .map()/.flatMap() way to chain operations
// without manually unwrapping and re-checking at every step.

Why This Makes Testing Easier

  • A pure function needs no mocking of a database, network, or clock -- call it with sample inputs, assert on the output.

  • No cleanup of mutated global state needed between tests -- there's nothing shared to reset.

  • Referential transparency means a function call can be replaced with its result in reasoning about behavior -- makes tests (and code review) easier to follow.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free