JavaScript Partial Application
Introduction
Partial application is an essential technique in functional programming that allows you to transform a function requiring multiple arguments into a series of functions that each take a subset of those arguments. When you partially apply a function, you pre-fill some of its arguments, creating a new function with the remaining parameters.
Unlike currying (which breaks down a function into a sequence of functions, each taking a single argument), partial application takes a function and binds values to one or more arguments, returning a new function that accepts the remaining arguments.
In this tutorial, we'll explore how partial application works in JavaScript, why it's useful, and how to implement and use it effectively.
Understanding Partial Application
Basic Concept
Imagine having a function that takes multiple arguments, but you find yourself repeatedly calling it with some of the same arguments. Partial application allows you to "lock in" those repeated arguments, creating a specialized version of the function that only needs the remaining parameters.
Let's see a simple example:
// Original function with three arguments
function add(a, b, c) {
return a + b + c;
}
// Manually creating a partially applied function
function addWithFive(b, c) {
return add(5, b, c);
}
// Usage
console.log(addWithFive(10, 20)); // Output: 35 (5 + 10 + 20)
In this example, we've manually created a partially applied version of add
where the first argument is fixed to 5
. The new function addWithFive
only needs two arguments instead of three.
Implementing Partial Application in JavaScript
Manual Implementation
Let's create a basic partial
utility function that makes it easier to partially apply functions:
function partial(func, ...fixedArgs) {
return function(...remainingArgs) {
return func(...fixedArgs, ...remainingArgs);
};
}
// Original function
function multiply(a, b, c) {
return a * b * c;
}
// Creating a partially applied function
const multiplyByTwo = partial(multiply, 2);
console.log(multiplyByTwo(3, 4)); // Output: 24 (2 * 3 * 4)
// We can partially apply multiple arguments
const multiplyTwoAndThree = partial(multiply, 2, 3);
console.log(multiplyTwoAndThree(4)); // Output: 24 (2 * 3 * 4)
Our partial
function takes a function and any number of fixed arguments. It returns a new function that will concatenate the fixed arguments with any additional arguments when called.
Using Function.prototype.bind
JavaScript provides a built-in mechanism for partial application through the bind
method:
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
// Using bind for partial application
const sayHello = greet.bind(null, "Hello");
console.log(sayHello("John")); // Output: "Hello, John!"
// We can't skip arguments with bind - they must be in order
const greetJohn = greet.bind(null, "Greetings");
console.log(greetJohn("John")); // Output: "Greetings, John!"
The first argument to bind
sets the this
context (which we set to null
since we don't need it), and subsequent arguments become the fixed parameters.
Advanced Partial Application
Placeholder Technique
Sometimes you might want to fix arguments in arbitrary positions, not just from left to right. We can create a more flexible partial application utility with placeholders:
// Define a placeholder symbol
const _ = Symbol('placeholder');
function partialWithPlaceholders(func, ...boundArgs) {
return function(...calledArgs) {
// Create a copy of boundArgs
const args = [...boundArgs];
let calledArgsIndex = 0;
// Fill placeholders with called arguments
for (let i = 0; i < args.length && calledArgsIndex < calledArgs.length; i++) {
if (args[i] === _) {
args[i] = calledArgs[calledArgsIndex++];
}
}
// Add any remaining called arguments
while (calledArgsIndex < calledArgs.length) {
args.push(calledArgs[calledArgsIndex++]);
}
// Check if we still have placeholders
if (args.includes(_)) {
// Still have placeholders, return a partially applied function
return partialWithPlaceholders(func, ...args);
}
// No more placeholders, call the function
return func(...args);
};
}
// Example usage
function divide(a, b) {
return a / b;
}
// Partially apply the second argument (b)
const divideBy10 = partialWithPlaceholders(divide, _, 10);
console.log(divideBy10(20)); // Output: 2 (20 / 10)
// Partially apply the first argument (a)
const divide5By = partialWithPlaceholders(divide, 5, _);
console.log(divide5By(2)); // Output: 2.5 (5 / 2)
This more powerful implementation allows you to use placeholders (_
) to specify which arguments you want to fix and which you want to leave for later.
Real-World Applications
Configuration Functions
Partial application is excellent for creating configuration functions:
function fetchFromAPI(baseURL, endpoint, params) {
const url = `${baseURL}/${endpoint}?${new URLSearchParams(params)}`;
return fetch(url)
.then(response => response.json());
}
// Create a configured API client
const fetchFromMyAPI = partial(fetchFromAPI, "https://api.myservice.com");
// Later usage is simpler and more focused
fetchFromMyAPI("users", { active: true })
.then(users => console.log(users));
fetchFromMyAPI("products", { category: "electronics" })
.then(products => console.log(products));
Event Handling
You can use partial application to simplify event handlers:
function handleItemAction(action, itemId, event) {
event.preventDefault();
console.log(`Performing ${action} on item ${itemId}`);
// Actual logic would go here
}
// Create specific handlers for different actions
function setupItemActions() {
const items = document.querySelectorAll('.item');
items.forEach(item => {
const id = item.dataset.id;
// Each handler is a partially applied function
item.querySelector('.edit-btn').addEventListener(
'click',
partial(handleItemAction, 'edit', id)
);
item.querySelector('.delete-btn').addEventListener(
'click',
partial(handleItemAction, 'delete', id)
);
});
}
Logging and Debugging
Partial application can create specialized logging functions:
function log(level, context, message, ...args) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level}] [${context}]: ${message}`, ...args);
}
// Create specialized loggers
const logError = partial(log, "ERROR");
const logInfo = partial(log, "INFO");
// Create context-specific loggers
const logAuthError = partial(log, "ERROR", "Authentication");
const logDatabaseInfo = partial(log, "INFO", "Database");
// Usage
logError("Payment", "Payment failed", { userId: 123, amount: 50 });
// [2023-09-15T12:34:56.789Z] [ERROR] [Payment]: Payment failed { userId: 123, amount: 50 }
logDatabaseInfo("Connection established");
// [2023-09-15T12:34:57.789Z] [INFO] [Database]: Connection established
Partial Application vs Currying
Although related, partial application and currying are different concepts:
- Partial Application: Creates a new function by fixing a set of arguments to an existing function.
- Currying: Transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.
// Partial application example (review)
function multiply(a, b, c) {
return a * b * c;
}
const multiplyByTwo = partial(multiply, 2);
console.log(multiplyByTwo(3, 4)); // 24
// Currying example for comparison
function curriedMultiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
console.log(curriedMultiply(2)(3)(4)); // 24
The key difference is that a partially applied function can take multiple remaining arguments at once, while a curried function takes exactly one argument at each step.
Performance Considerations
Partial application creates new function instances, which can affect memory usage if overused. However, the benefits often outweigh these costs:
- Optimizations: Modern JavaScript engines optimize function creation patterns
- Reusability: The memory cost is often worth the code clarity and reusability
- Reference Sharing: Partially applied functions can be stored in variables and reused
Summary
Partial application is a powerful functional programming technique that allows you to create specialized functions by fixing some arguments of a more general function. Key takeaways include:
- Partial application creates new functions with some arguments pre-filled
- It can be implemented using custom utilities or
Function.prototype.bind
- Advanced implementations can support placeholders for greater flexibility
- Real-world applications include API clients, event handlers, and logging
- Unlike currying, partial application allows for multiple remaining arguments
By using partial application in your JavaScript code, you can create more reusable, composable, and readable functions.
Exercises
- Create a
formatCurrency
function that takes a currency symbol and an amount, then create partially applied functions for USD, EUR, and GBP. - Implement a
debounce
function and use partial application to create variations with different timeout values. - Build a more advanced placeholder-based partial application utility that supports multiple placeholders in any position.
- Create a math library with basic operations (add, subtract, multiply, divide) and use partial application to create specialized functions like
increment
,double
, andhalve
.
Additional Resources
- Functional Programming in JavaScript by Marijn Haverbeke
- JavaScript Allongé by Reg Braithwaite
- Professor Frisby's Mostly Adequate Guide to Functional Programming
- Functional-Light JavaScript by Kyle Simpson
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)