Function Currying and Partial Application
Creating specialized functions through currying and partial application
Challenge
Section titled “Challenge”You have utility functions that take multiple parameters. You frequently call them with some parameters pre-filled. How can you create specialized versions without writing wrapper functions?
Explanation
Section titled “Explanation”Currying transforms a function that takes multiple arguments into a sequence of functions that each take one argument. Partial application fills in some arguments to create a new function.
Currying Basics
Section titled “Currying Basics”// Regular functionfunction add(a, b, c) { return a + b + c;}
console.log(add(1, 2, 3)); // 6
// Curried versionfunction curriedAdd(a) { return function(b) { return function(c) { return a + b + c; }; };}
console.log(curriedAdd(1)(2)(3)); // 6
// Practical curryingconst add1 = curriedAdd(1); // Function waiting for b and cconst add1And2 = add1(2); // Function waiting for cconst result = add1And2(3); // 6Creating Curried Functions
Section titled “Creating Curried Functions”// Utility to curry any functionfunction curry(fn) { const arity = fn.length; // Number of parameters
return function curried(...args) { if (args.length >= arity) { return fn.apply(null, args); }
return function(...nextArgs) { return curried(...args, ...nextArgs); }; };}
// Using curryfunction multiply(a, b, c) { return a * b * c;}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24console.log(curriedMultiply(2, 3)(4)); // 24console.log(curriedMultiply(2)(3, 4)); // 24Solution: API Request Builder
Section titled “Solution: API Request Builder”const curry = (fn) => { const arity = fn.length; return function curried(...args) { if (args.length >= arity) return fn(...args); return (...nextArgs) => curried(...args, ...nextArgs); };};
// API request functionfunction apiRequest(baseUrl, endpoint, method, data) { return fetch(`${baseUrl}${endpoint}`, { method, body: JSON.stringify(data) });}
const curriedRequest = curry(apiRequest);
// Create specialized request functionsconst apiV1 = curriedRequest('https://api.example.com');const apiV1Users = apiV1('/users');const getUsers = apiV1Users('GET');
// UsagegetUsers({}).then(res => res.json());
// Or create POST request builderconst postUsers = apiV1Users('POST');postUsers({ name: 'John' }).then(res => res.json());Partial Application
Section titled “Partial Application”// Partial application functionfunction partial(fn, ...partialArgs) { return function(...args) { return fn(...partialArgs, ...args); };}
// Or using bindfunction multiply(a, b, c) { return a * b * c;}
const double = multiply.bind(null, 2);console.log(double(3, 4)); // 24
// Practical example: Filteringconst users = [ { name: 'Alice', age: 30, active: true }, { name: 'Bob', age: 25, active: false }, { name: 'Charlie', age: 35, active: true }];
function filterBy(prop, value, item) { return item[prop] === value;}
const filterActive = partial(filterBy, 'active', true);const activeUsers = users.filter(filterActive);
// Or with curryingconst curriedFilter = (prop) => (value) => (item) => { return item[prop] === value;};
const isActive = curriedFilter('active')(true);const activeUsers2 = users.filter(isActive);Function Composition
Section titled “Function Composition”// Compose functionsfunction compose(...fns) { return (value) => { return fns.reduceRight((acc, fn) => fn(acc), value); };}
// Pipe functions (left-to-right)function pipe(...fns) { return (value) => { return fns.reduce((acc, fn) => fn(acc), value); };}
// Example functionsconst double = (n) => n * 2;const addTen = (n) => n + 10;const square = (n) => n * n;
// compose: square(addTen(double(5)))const composedFn = compose(square, addTen, double);console.log(composedFn(5)); // (5*2+10)^2 = 400
// pipe: double -> addTen -> squareconst pipedFn = pipe(double, addTen, square);console.log(pipedFn(5)); // ((5*2)+10)^2 = 400Practical Example: Data Transformation Pipeline
Section titled “Practical Example: Data Transformation Pipeline”const curry = (fn) => { const arity = fn.length; return function curried(...args) { if (args.length >= arity) return fn(...args); return (...nextArgs) => curried(...args, ...nextArgs); };};
// Data transformationsconst map = curry((fn, arr) => arr.map(fn));const filter = curry((predicate, arr) => arr.filter(predicate));const reduce = curry((fn, initial, arr) => arr.reduce(fn, initial));
// Create specialized functionsconst mapDouble = map(x => x * 2);const filterEven = filter(x => x % 2 === 0);const sumAll = reduce((a, b) => a + b, 0);
// Build pipelineconst numbers = [1, 2, 3, 4, 5];const result = sumAll( filterEven( mapDouble(numbers) ));// Numbers: 1,2,3,4,5// After double: 2,4,6,8,10// After filter even: 2,4,6,8,10 (all are even)// After sum: 30
console.log(result); // 30Performance Note
Section titled “Performance Note”// Currying has a small overhead from creating intermediate functionsconst curry = (fn) => { const arity = fn.length; return function curried(...args) { if (args.length >= arity) return fn(...args); return (...nextArgs) => curried(...args, ...nextArgs); };};
// For performance-critical code, direct calls might be betterconst add = (a, b, c) => a + b + c;console.log(add(1, 2, 3)); // Faster than curried version
// Currying is most valuable for:// - API design (more readable)// - Function composition// - Reusable utilities// - Higher-order functions