Functional Programming Tutorial
Currying is a functional programming technique that transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Named after mathematician Haskell Curry, it's a powerful pattern for creating flexible, reusable code.
// Regular function with multiple arguments
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
// Curried version
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
addCurried(1)(2)(3); // 6
Let's see how a simple two-argument function transforms into a curried function:
// Before: Regular function
function multiply(x, y) {
return x * y;
}
multiply(3, 4); // 12
// After: Curried function
function multiplyCurried(x) {
return function(y) {
return x * y;
};
}
multiplyCurried(3)(4); // 12
// Or with arrow functions (more concise)
const multiplyCurried = x => y => x * y;
multiplyCurried(3)(4); // 12
Create specialized functions by pre-filling some arguments:
const multiply = x => y => x * y;
// Create specialized functions
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Reuse them
console.log(double(10)); // 20
console.log(triple(10)); // 30
Curried functions compose beautifully:
const add = x => y => x + y;
const multiply = x => y => x * y;
// Create a pipeline
const addThenDouble = x => multiply(2)(add(5)(x));
console.log(addThenDouble(3)); // (3 + 5) * 2 = 16
Configure functions once, use them many times:
const greet = greeting => name => `${greeting}, ${name}!`;
// Configure different greetings
const sayHello = greet('Hello');
const sayHi = greet('Hi');
const sayGoodbye = greet('Goodbye');
console.log(sayHello('Alice')); // "Hello, Alice!"
console.log(sayHi('Bob')); // "Hi, Bob!"
console.log(sayGoodbye('Charlie')); // "Goodbye, Charlie!"
// Curried logger function
const log = level => message => timestamp => {
console.log(`[${timestamp}] ${level}: ${message}`);
};
// Create specialized loggers
const errorLog = log('ERROR');
const infoLog = log('INFO');
const debugLog = log('DEBUG');
// Use them
const now = new Date().toISOString();
errorLog('Database connection failed')(now);
infoLog('User logged in')(now);
debugLog('Processing request')(now);
// Or create even more specialized versions
const errorNow = errorLog('Something went wrong');
errorNow(new Date().toISOString());
errorNow(new Date().toISOString()); // Can reuse!
const apiRequest = method => endpoint => data => {
return fetch(endpoint, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
};
// Create method-specific functions
const get = apiRequest('GET');
const post = apiRequest('POST');
const put = apiRequest('PUT');
const del = apiRequest('DELETE');
// Create endpoint-specific functions
const getUsers = get('/api/users');
const postUser = post('/api/users');
const updateUser = put('/api/users');
// Use them
getUsers(null).then(res => res.json());
postUser({ name: 'Alice', email: 'alice@example.com' });
updateUser({ id: 1, name: 'Alice Updated' });
const applyDiscount = discountPercent => price => {
return price - (price * discountPercent / 100);
};
// Create different discount functions
const apply10Percent = applyDiscount(10);
const apply20Percent = applyDiscount(20);
const apply50Percent = applyDiscount(50);
// Use them
console.log(apply10Percent(100)); // 90
console.log(apply20Percent(100)); // 80
console.log(apply50Percent(100)); // 50
// Apply to multiple items
const prices = [100, 200, 300];
const discountedPrices = prices.map(apply20Percent);
console.log(discountedPrices); // [80, 160, 240]
// Three arguments
const formatDate = year => month => day => {
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
};
console.log(formatDate(2024)(3)(15)); // "2024-03-15"
// Partially apply
const format2024 = formatDate(2024);
const format2024March = format2024(3);
console.log(format2024March(1)); // "2024-03-01"
console.log(format2024March(15)); // "2024-03-15"
console.log(format2024March(31)); // "2024-03-31"
// Four arguments
const createUser = firstName => lastName => email => age => ({
firstName,
lastName,
email,
age,
fullName: `${firstName} ${lastName}`
});
const createSmith = createUser('John')('Smith');
const johnSmithGmail = createSmith('john.smith@gmail.com');
console.log(johnSmithGmail(30));
// { firstName: 'John', lastName: 'Smith', email: '...', age: 30, fullName: 'John Smith' }
// Regular function - must provide all arguments at once
function calculateTotal(price, quantity, taxRate) {
return price * quantity * (1 + taxRate);
}
calculateTotal(10, 5, 0.08); // Must provide all three
// Curried version - flexible application
const calculateTotalCurried = price => quantity => taxRate => {
return price * quantity * (1 + taxRate);
};
// Can apply arguments one at a time
const priceSet = calculateTotalCurried(10);
const quantitySet = priceSet(5);
const total = quantitySet(0.08);
// Or create reusable configurations
const calculateWithTax = calculateTotalCurried(10)(5);
console.log(calculateWithTax(0.08)); // 8% tax
console.log(calculateWithTax(0.10)); // 10% tax
console.log(calculateWithTax(0.15)); // 15% tax
f(a, b, c) into f(a)(b)(c)a => b => c => resultYou've learned the fundamentals of currying in JavaScript! Currying is a powerful technique for creating reusable, composable functions. Start applying it in your projects where you need configuration, partial application, or function composition.
Practice tip: Try converting some of your existing multi-parameter functions into curried versions and see where it improves code reusability.