Skip to main content

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:

javascript
// 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:

javascript
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:

javascript
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:

javascript
// 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:

javascript
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:

javascript
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:

javascript
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:

  1. Partial Application: Creates a new function by fixing a set of arguments to an existing function.
  2. Currying: Transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.
javascript
// 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:

  1. Optimizations: Modern JavaScript engines optimize function creation patterns
  2. Reusability: The memory cost is often worth the code clarity and reusability
  3. 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

  1. Create a formatCurrency function that takes a currency symbol and an amount, then create partially applied functions for USD, EUR, and GBP.
  2. Implement a debounce function and use partial application to create variations with different timeout values.
  3. Build a more advanced placeholder-based partial application utility that supports multiple placeholders in any position.
  4. Create a math library with basic operations (add, subtract, multiply, divide) and use partial application to create specialized functions like increment, double, and halve.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)