Skip to content
Plurafy

Function Currying and Partial Application

Creating specialized functions through currying and partial application

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?

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.

// Regular function
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// Curried version
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
// Practical currying
const add1 = curriedAdd(1); // Function waiting for b and c
const add1And2 = add1(2); // Function waiting for c
const result = add1And2(3); // 6
// Utility to curry any function
function 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 curry
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
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 function
function apiRequest(baseUrl, endpoint, method, data) {
return fetch(`${baseUrl}${endpoint}`, {
method,
body: JSON.stringify(data)
});
}
const curriedRequest = curry(apiRequest);
// Create specialized request functions
const apiV1 = curriedRequest('https://api.example.com');
const apiV1Users = apiV1('/users');
const getUsers = apiV1Users('GET');
// Usage
getUsers({}).then(res => res.json());
// Or create POST request builder
const postUsers = apiV1Users('POST');
postUsers({ name: 'John' }).then(res => res.json());
// Partial application function
function partial(fn, ...partialArgs) {
return function(...args) {
return fn(...partialArgs, ...args);
};
}
// Or using bind
function multiply(a, b, c) {
return a * b * c;
}
const double = multiply.bind(null, 2);
console.log(double(3, 4)); // 24
// Practical example: Filtering
const 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 currying
const curriedFilter = (prop) => (value) => (item) => {
return item[prop] === value;
};
const isActive = curriedFilter('active')(true);
const activeUsers2 = users.filter(isActive);
// Compose functions
function 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 functions
const 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 -> square
const pipedFn = pipe(double, addTen, square);
console.log(pipedFn(5)); // ((5*2)+10)^2 = 400

Practical 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 transformations
const 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 functions
const mapDouble = map(x => x * 2);
const filterEven = filter(x => x % 2 === 0);
const sumAll = reduce((a, b) => a + b, 0);
// Build pipeline
const 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); // 30
// Currying has a small overhead from creating intermediate functions
const 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 better
const 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