Lodash _.curry with ES6 Rest Parameters

Applying Lodash's _.curry method to a function that uses ES6 rest parameters causes the curried function to execute immediately upon its first invocation, failing to curry the arguments as expected. Because _.curry relies on JavaScript's native Function.prototype.length property to determine how many arguments to collect, and rest parameters do not count toward this property, the curried function detects an arity of zero. This article explains the underlying mechanics of this behavior, illustrates the resulting issues with code examples, and provides the standard solution to make currying work properly with rest parameters.

The Role of Function Arity in Lodash

In JavaScript, _.curry creates a function that accepts one or more arguments of the target function. If the number of provided arguments is less than the target function's expected argument count (its arity), it returns a new function expecting the remaining arguments. Once the required number of arguments is met, the underlying function executes.

To determine this arity automatically, Lodash inspects the function's .length property:

function regularFunction(a, b, c) {}
console.log(regularFunction.length); // 3

Because regularFunction.length is 3, _.curry(regularFunction) waits until it has received three arguments across its invocations before returning the final result.

How ES6 Rest Parameters Affect Function Length

The ECMAScript 6 specification states that rest parameters (...args) do not contribute to a function's .length property. Only standard parameters declared before the first default parameter or rest parameter are counted.

Consider the following examples:

const onlyRest = (...args) => args;
console.log(onlyRest.length); // 0

const mixed = (a, b, ...rest) => [a, b, ...rest];
console.log(mixed.length); // 2

When a function relies entirely on rest parameters to receive its inputs, its .length is always 0.

The Result of Applying _.curry to Rest Parameters

When _.curry wraps a function that uses only rest parameters, it reads func.length as 0. Because the detected arity threshold is 0, _.curry considers the argument requirements satisfied immediately upon the very first call, regardless of how many arguments were supplied.

const _ = require('lodash');

const sum = (...numbers) => numbers.reduce((acc, n) => acc + n, 0);
const curriedSum = _.curry(sum);

// Intended curried usage:
const addFive = curriedSum(5); 

// Actual result:
console.log(addFive); // Output: 5 (number), not a partially applied function

Instead of returning a new function that waits for subsequent numbers, curriedSum(5) executes immediately and outputs 5. Subsequent calls intended to provide additional arguments will fail with a TypeError because the returned value is a primitive result rather than a function.

The Solution: Explicit Arity

To properly curry a function that utilizes rest parameters, you must bypass Lodash's automatic .length inspection. Lodash supports this via an optional second argument in _.curry, which explicitly defines the expected arity:

const _ = require('lodash');

const sumThree = (...numbers) => numbers.reduce((acc, n) => acc + n, 0);

// Explicitly set the arity to 3
const curriedSumThree = _.curry(sumThree, 3);

const step1 = curriedSumThree(1);       // Returns a function
const step2 = step1(2);                 // Returns a function
const finalResult = step2(3);           // Returns 6

console.log(finalResult); // 6

By providing the explicit arity argument, _.curry ignores the 0 reported by the rest parameter's .length property and successfully delays execution until the specified number of arguments are accumulated.