Structure your code with currying, function composition, and immutable data to gain predictability and testability.
Open this lesson in KodokonJavaScript treats functions as first-class values, which opens the door to functional patterns. Currying turns a function of n arguments into a chain of single-argument functions: f(a, b, c) becomes f(a)(b)(c). Not to be confused with partial application, which fixes some arguments in a single step (fn.bind(null, a)). Currying shines at creating specialized functions from generic ones.
const curry = (fn) => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) return fn(...args);
return (...rest) => curried(...args, ...rest);
};
};
const add3 = (a, b, c) => a + b + c;
const addCurried = curry(add3);
console.log(addCurried(1)(2)(3)); // 6
console.log(addCurried(1, 2)(3)); // 6A spec subtlety: fn.length only counts the parameters located before the first default or rest parameter. (a, b = 1) => {} has a length of 1, (...args) => {} of 0. An arity-based curry must therefore be given functions with simple parameters. Composition is the next step: assembling small pure functions into readable pipelines.
const pipe = (...fns) => (input) =>
fns.reduce((acc, fn) => fn(acc), input);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const dashes = (s) => s.replace(/\s+/g, "-");
const toSlug = pipe(trim, lower, dashes);
console.log(toSlug(" Hello World "));
// "hello-world"pipe(f, g, h)(x) computes h(g(f(x))): you read it in execution order. The compose variant applies right to left, like mathematical notation. These pipelines are only valuable if the functions are pure - no side effects, no hidden dependencies - which calls for immutable data: instead of mutating an object, you produce a new version of it, and reference equality is then enough to detect a change (the heart of React's reconciliation).
const state = Object.freeze({
user: { name: "Ada" },
tags: ["js"]
});
const next = {
...state,
tags: [...state.tags, "fp"]
};
const deepCopy = structuredClone(state);
deepCopy.user.name = "Grace"; // independent copybind, currying only with arrow functionspipe(f, g, h)(x) equivalent to?const s = Object.freeze({ a: { b: 1 } }), does the assignment s.a.b = 2 work?s.a is not frozen