Implementation of lodash ‘curry’ function

very simple, very basic

Kaijie Huang
4 min readApr 9, 2017
Jiang, a 21st century cewebrity of China

When I read Eric Elliott’s medium about functional composition, I get quite confused about his implementation of the ‘curry’ function, which seems to be a simple mimic of the lodash.js curry function, and it is in ES6.

const curry = fn => (…args) => fn.bind(null, …args);

To help other UI/UX developers understand what’s going on behind this line of code, I decide to write this medium and try to use some very simple and intuitive, sometimes naive, examples to illustrate the basic implementation of the curry function step by step.

Let’s first check the document of lodash.js to see what a real curry does.

var abc = function(a, b, c) { return [a, b, c];};var curried = _.curry(abc);curried(1)(2)(3); // => [1, 2, 3]curried(1, 2)(3); // => [1, 2, 3]curried(1, 2, 3); // => [1, 2, 3]// Curried with placeholders.curried(1)(_, 3)(2); // => [1, 2, 3]

In my understanding, basically, curry lets us:

  1. Collect arguments step by step in several function calls instead of one.
  2. When enough arguments are collected, return the function result.

To better understand it I have checked several implementation samples online. However, instead of starting from the final implementation, I wish there is a very simple tutorial starting from a very basic example, like the one below.

Understanding the function fn is the beginning point of all. Basically, this function works as a “arguments collector”. Every time this function is called, it will return a bound function (fb) of itself, and bind the provided “arguments” of this function to that return function. The “arguments” will precede any provided arguments later when that return bound function is called. Thus the arguments in each call will be gradually collected into an array.

Of course, like the curry function, we don’t need to keep collecting forever. Now we can just hard-code a stop point.

To make it behavior like a curry function, two problems need to be solved:

  1. Instead of log out the arguments at the end by passing them to console.log, we want to pass the collected arguments to the target function who needs them.
  2. The numOfRequiredArguments should not be hard-coded, it should be the number of arguments expected by the target function.

Fortunately, Javascript function does come with a property called “length”, which specifies the number of arguments expected by the function. Thus instead of hard-coded, we can always use this property to determine the
number of required arguments. The second problem is solved!

What about the first problem: maintain a reference to target function?

There are several examples online to tackle this issue. They are slightly different but share the same idea: besides storing the arguments, we also need to store the reference to the target function somewhere.

Here I categorize them into 2 different approaches. Again, they are more or less similar, but understanding them can help us better understand the logic behind the scene. BTW, I call the function magician here instead of curry.

Approach #1

what magician function does is: it receives the target function as argument, then it returns the ‘arguments collector’ function, same as the fn shown above. The only difference is that, when the number of collected arguments matches the number of required arguments of the target function, it will apply those collected arguments to that target function and return the calculated result. This approach solves the first problem(reference to the target function) by storing it in the closure created by magician.

Approach #2

This approach takes one step further. Since the arguments collector function is just a normal function, why not just use magician itself as the arguments collector?

Notice one difference in the approach#2. Because magician accepts the target function as its first argument, thus the collected arguments will always include that function as arguments[0]. As a result, we need to trim that first argument off when checking the total number of valid arguments.

BTW, since the target function is passed to magician recursively, so instead of using closure for storing the target function reference, the target function is referenced explicitly by the passed in first argument.

As you can see, the “curry” function used by Eric Elliott’s above is similar to approach #1, but it is really a partial application (another story).

const curry = fn => (…args) => fn.bind(null, …args);

above is a curry which returns the ‘arguments collector’ that only collect arguments once and return the bound target function.

One More Step

The ‘magician’ above is still not as magic as the ‘curry’ function of lodash.js. The curry of lodash allows ‘_’ as placeholder for input arguments.

curried(1)(_, 3)(2); // => [1, 2, 3], notice the placeholder '_'

To achieve the placeholder functionality, there is an implicit requirement: We need to know which arguments are preset to the bound function, and which are the additional arguments provided explicitly when calling the function (let’s call them “added” arguments).

This can be done by creating another closure.

The fn2 above is almost the same fn , which works like an ‘arguments collector’. However, instead of returning the bound function directly, fn2 returns an intermediate helper function. The helper function is not bound, and thus it can be used to separate the “preset” arguments and “added” arguments.

Of course, instead concating the “preset” and “added” by [...preset, ...added] , we need to do some modification when combining them. We need to search for the location of the placeholder in the “preset” and replace it with a valid “added” argument. I haven’t checked how lodash makes it, but below is a simple implementation to achieve the similar functionality.

line #15 to #24 is the logic used to put the “added” arguments into the right position in the “preset” array: either a placeholder, or the end of the the preset. That position is marked as nextPos and initialized as 0 index.

Now, the magician3 works almost the same as the curry function of lodash.

--

--