Implementing Currying with Placeholders in JavaScript



What is Currying with Placeholders?

Currying is a technique in functional programming where a function with multiple arguments is transformed into a sequence of functions, each with a single argument. Placeholders add an extra layer of flexibility by allowing you to skip certain arguments and provide them later.

Real Interview Insights

Interviewers might ask you to:

  • Implement a currying function with placeholder support.
  • Explain the advantages of using placeholders in currying.
  • Demonstrate how to use currying with placeholders in practical scenarios.

Implementing Currying with Placeholders

Let's start with an implementation of a currying function that supports placeholders:

function curry(func) {
  return function curried(...args) {
    const placeholder = curry.placeholder;
    const validArgs = args.filter(arg => arg !== placeholder);
 
    if (validArgs.length >= func.length) {
      return func.apply(this, validArgs);
    } else {
      return function(...nextArgs) {
        const combinedArgs = args.map(arg => arg === placeholder && nextArgs.length ? nextArgs.shift() : arg).concat(nextArgs);
        return curried.apply(this, combinedArgs);
      };
    }
  };
}
 
curry.placeholder = '_';
Explanation:
  • Placeholder Definition: Define a placeholder (curry.placeholder) to represent skipped arguments.
  • Valid Arguments: Filter out placeholder values to determine if the number of provided arguments meets the original function's arity.
  • Combined Arguments: Combine the current arguments with the next set of arguments, replacing placeholders with actual values as they are provided.

Practical Example

Consider a function that subtracts three numbers:

function subtract(a, b, c) {
  return a - b - c;
}
 
const curriedSubtract = curry(subtract);
 
console.log(curriedSubtract(10, '_', 5)(3)); // Output: 2
console.log(curriedSubtract('_', 3)(10, 5)); // Output: 2
console.log(curriedSubtract(10)('_', 5)(3)); // Output: 2

In this example:

  • The subtract function is curried using the curry function.
  • The placeholder (_) is used to skip arguments, allowing them to be provided later.

Advanced Currying: Handling Multiple Use Cases with Placeholders

Let's further enhance our currying function to handle more complex scenarios:

function curry(func) {
  return function curried(...args) {
    const placeholder = curry.placeholder;
 
    const hasPlaceholder = args.includes(placeholder);
    const hasAllArgs = args.filter(arg => arg !== placeholder).length >= func.length;
 
    if (hasAllArgs && !hasPlaceholder) {
      return func.apply(this, args);
    } else {
      return function(...nextArgs) {
        const combinedArgs = args.map(arg => arg === placeholder && nextArgs.length ? nextArgs.shift() : arg).concat(nextArgs);
        return curried.apply(this, combinedArgs);
      };
    }
  };
}
 
curry.placeholder = '_';
 
// Example with varying arguments and placeholders
function multiply(a, b, c, d) {
  return a * b * c * d;
}
 
const curriedMultiply = curry(multiply);
 
console.log(curriedMultiply(2)('_', 4)(3, '_')(5)); // Output: 120
console.log(curriedMultiply('_', 3)(2, '_')(4, 5)); // Output: 120
Key Points:
  • Placeholder Handling: Ensure that the function checks for placeholders and combines arguments appropriately.
  • Flexible Argument Handling: Allow the function to be called with arguments in any order, using placeholders to skip and provide arguments later.

Coding Challenge: Currying with Multiple Placeholders

For a deeper understanding, try this coding challenge:

Challenge: Modify the currying function to handle multiple placeholders, allowing for more complex argument patterns.

function curry(func) {
  return function curried(...args) {
    const placeholder = curry.placeholder;
 
    const hasPlaceholder = args.includes(placeholder);
    const validArgs = args.filter(arg => arg !== placeholder);
    const hasAllArgs = validArgs.length >= func.length;
 
    if (hasAllArgs && !hasPlaceholder) {
      return func.apply(this, args);
    } else {
      return function(...nextArgs) {
        let index = 0;
        const combinedArgs = args.map(arg => arg === placeholder && nextArgs[index] !== undefined ? nextArgs[index++] : arg).concat(nextArgs.slice(index));
        return curried.apply(this, combinedArgs);
      };
    }
  };
}
 
curry.placeholder = '_';
 
// Example usage with multiple placeholders
function add(a, b, c, d) {
  return a + b + c + d;
}
 
const curriedAdd = curry(add);
 
console.log(curriedAdd(1)('_', 3, '_')(2)('_')(4)); // Output: 10
console.log(curriedAdd('_', 2)('_', 4)(1, '_')(3)); // Output: 10

In this challenge:

  • Enhance the currying function to handle multiple placeholders, allowing arguments to be provided in any order and combination.