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); // 3Because 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); // 2When 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 functionInstead 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); // 6By 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.