DEV Community 👩‍💻👨‍💻

Abdullah Numan
Abdullah Numan

Posted on

Auto-currying in JavaScript

Auto-currying in JavaScript

In this post, we explore auto-currying in JavaScript.

This is the fourth article in the series titled Curry Functions in JavaScript. It follows the previous article named Currying Existing Functions with JavaScript, where we mentioned that curried functions can be of various complexities, such as auto-curried unary functions, variadic ones, infinitely curried functions, etc.

This post will cover auto-currying with a purely unary implementation.

Auto-currying, what the Heck ?

Auto-currying is an advanced currying implementation technique. It is an in-built feature in Haskell, where a multary function has to be curried by the compiler, because all functions are unary to begin with. So, a multary function always gets auto-curried in Haskell.

In JavaScript, though, functions are not restricted to be unary. We're currying stuff for the heck of it, because it's exciting and fun. We don't have auto-currying in JavaScript, because we literally don't need it. And just for the record, we might not call currying in JavaScript "currying" in the actual sense. By the mid of the next article, we'll find out we're just fooling around.

We want to implement currying ourselves. Obviously, it won't be auto-currying in the sense that our functions should be curried automatically by the compiler; rather that we are making the transformation with our own code by moving it to runtime. That is, instead of manually defining a currying function for each passed-in multary function, we are automating the task of transformation for that function with a wrapper and a couple of helpers.

We can do this by making use of plain JavaScript function composition along with recursion. And in the later posts we will see that we can achieve the same transformation with native JavaScript methods like Function.prototype.call, Function.prototype.apply, Function.prototype.bind as well.

Automating the Currying Process

In the previous post, Currying Existing Functions with JavaScript, we saw that we used a wrapper function that acts as an accumulator of arguments by leveraging nested closures. The accumulator function plays the central role in currying. We need one in every currying transformation.

However, in auto-currying we don't use a nested functions system. Instead, we use recursion. We track the number of arguments passed, compare it to the multary function's arity, and use the comparison to decide whether we should keep accepting more arguments by returning the accumulator recursively. Once we have enough arguments, we terminate the accumulator and return a call to the to-be-curried function.

The Plan
The key idea is to allow accepting only one argument at a time - as unarity is the essence of currying. Then we go ahead and store the arguments in an array, whose copy should be always accessible from the current execution context of the recursive stack.

Example

In the following example, we define the accumulator to accept only one argument at a time:

function curry(f) {
  function curried(args) {
    if (args.length >= f.length) return f(...args);
    return accumulator;

    function accumulator(a) {
      return curried([...args, a]);
    };
  };

  return curried([]);
};

Enter fullscreen mode Exit fullscreen mode

Notice that we are declaring accumulator() inside the curried() helper function to give it access to the args array so that an updated args can be passed on to successive recursive calls.

Notice also that we are using args in the parameter of curry() as an array and not as a list spread from the array: ...args. And we pass an empty array to the first call on curried().

And now, we want to curry our createMessage() function with curry() at runtime and use it:

function createMessage(greeting, name, message) {
  return `${greeting}, ${name}! ${message}`;
};

const curriedCreateMessage = curry(createMessage);

console.log(curriedCreateMessage('Hi')('Haskell')('Whadup?')); // Hi, Haskell! Whadup?
console.log(curriedCreateMessage('Hi', 'Hello')('Haskell', 'Hasikell')('Whadup?', `What's up`));
// Hi, Haskell! Whadup?

Enter fullscreen mode Exit fullscreen mode

Notice the second log above ignores additional arguments and returns the same Hi, Haskell! Whadup? string as the first, despite being passed two arguments at each call.

We could have written the curry() function like this:

function curry(f) {
  return function curried(...args) {
    if (args.length >= f.length) return f(...args);
    return accumulator;

    function accumulator(a) {
      return curried(...args, a);
    };
  };
};
Enter fullscreen mode Exit fullscreen mode

But there is a problem with this for returning all-out unary functions. We'll come to that in the next article on variadic currying. But what's the problem?

Feel free to share your thoughts in the comments.

Top comments (2)

Collapse
 
jonrandy profile image
Jon Randy

It should be pointed out that this won't work as expected with functions that have optional parameters, since function.length doesn't include them.

Collapse
 
opauloh profile image
Paulo Henrique

well pointed

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.