Currying is one of the most formidable weapons in our functional programming arsenal. Combined with composition, it’s extremely powerful. But is currying useful if you’re doing object destructuring with your function parameters? Calle Kabo asked me about this recently on LinkedIn.

Hi James,

I love this article, it got me reading a bunch more of your articles. But there’s one thing I feel stuck on. How do you combine currying with destructuring assignments?

I want to be able to do something like this:

   const add = ({ a, b, c}) => a + b + c;
   const addC = curryDestructed(add);
   addC({a: 1, b: 1, c: 1});     // >> 3
   addC({a: 1})({b: 1})({c: 1}); // >> 3
   addC({c: 1})({b: 1, a: 1});   // >> 3

The example above is from https://yarnpkg.com/en/package/plura#currydestructed but that package doesn’t seem to be maintained unfortunately.

Curious to hear your thoughts on this :)

It’s an excellent question. And forgive me if I repeat things we already know. I want to make sure I understand the question. And sometimes that means stating the obvious.

As Calle points out, combining currying with destructuring is tricky. JavaScript sees an object as a single value. So our ordinary curry function doesn’t work. To make the problem concrete, let’s look at that add() example again.

const add = ({a, b, c}) => a + b + c;

Under normal circumstances, there’s no need to curry that function, as it only takes one parameter. At least, that’s how the computer sees it. But a human can look at that signature and see three named parameters. Getting the computer to do the same though is trickier. So Richard has done something really interesting here with Plura and curryDestructured().

Having a look at the source code is enlightening. What he’s doing is converting the function to a string. And then he parses it with a regular expression to create a map of the parameters it needs. With that in place, he can track which parameters have been filled in each time the function is called.

Now, that’s a lot of effort just to curry destructured object parameters. But if someone has already done the work for you, then why not go ahead and use it? If you can follow along with the source code, then there’s no reason not to rip out the 2–3 functions needed to make it work. You can keep them in your own personal toolkit and use as necessary. They will continue to work whether someone maintains the project or not.

If you’ve read (and understood) the source code, then you’ll be able to see some of the limitations of it. For example, it looks like that code will only work on functions that you can convert to a string. For example, the following might cause you problems:

const addAndLog = (msg, {a, b, c}) => {
    console.log(msg + ': ' + (a + b + c));
    return a + b + c;
};
const fooAdd = addAndLog.bind(null, 'Foo');
addAndLog.toString()
// ⦘ '(msg, {a, b, c}) => { console.log(msg + \': \' + (a + b + c)); return a + b + c; }'
fooAdd.toString()
// ⦘ 'function () { [native code] }'

That fooAdd() function doesn’t retain the function signature. So this approach of parsing the function string won’t work. That’s OK, so long as you know that going in.

Personally, I haven’t come across many cases where I’d want to use destructured currying. Think about what currying does. It takes polyadic (multi-parameter) functions and makes them unary (single-parameter) functions. But a function that expects an object is already unary. And there are other ways to manipulate objects before we pass them into a function.

To think it through, let’s look at that add() example again. Imagine we have an object with a set to 2 and another object with b set to 3. We want to create a function that takes an object with c set to something and adds them together. One way would be to use curryDestructured(). But I can achieve a similar effect with Object.assign or the spread operator.

const assign = x => y => Object.assign(x, y);
const add    = ({a, b, c}) => a + b + c;

const a2 = {a: 2};
const b3 = {b: 3};

const partiallyAppliedAdd = compose(
    add,
    assign(b3),
    assign(a2),
);

We can also write it like this:

const partiallyAppliedAdd = compose(
    add,
    assign(assign(a2, b3)),
);

It is a little bit more work than curryDestructured() but not much more. Either way, we still have to keep track of how many parameters we’ve applied. Otherwise, we don’t know when we can call the final function.

But, that curryDestructured() interface is still pretty nice… Maybe we could have our cake and eat it too. What if we created a new function that will work even when we can’t call .toString() on the function? The only caveat is that we’ll have to tell currying function which keys we need ahead of time. It might look something like this:

function difference(a, b) {
    return a.filter(function isInArray(i) {return b.indexOf(i) < 0;});
}

function keys(obj) {
    return Object.keys(obj);
}

function curryOnPattern(patternKeys, fn) {
    return function curriedFunc(obj) {
        const remainingKeys = difference(patternKeys, keys(obj));
        return (remainingKeys.length > 0)
            ? curryOnPattern(remainingKeys, compose(fn, assign(obj)))
            : fn(obj);
    }
}

const dCurriedAdd = curryOnPattern(['a', 'b', 'c'], add);
dCurriedAdd({a: 2})({b: 3})({c: 0}); // ← 5
dCurriedAdd({a: 2, b: 3})({c: 0});   // ← 5
dCurriedAdd({a: 2, b: 3, c: 0});     // ← 5

See it all together on JSBin

Of course, this is limited too. We’re assuming only flat objects. Writing a version that worked with nested destructuring might be a bit tricker.

So, in summary, currying destructured arguments doesn’t seem like a bad thing to want to do. But I think you can achieve the same effect with assign(). And that approach seems more robust because we’re not relying on .toString(). If that’s still too inconvenient, then we can write a robust destructured curry function that relies on us specifying which keys are needed. It’s not quite as flexible, but it’s pretty close.