Curry in a Bind

Development of curry functions using apply and bind methods

Introduction

This article documents a couple of experimental developments I conducted to investigate the functional programming concept called currying; named after Haskell Brooks Curry.

Wikipedia: Haskell Brooks Curry was an American mathematician and logician. Curry is best known for his work in combinatory logic.

Currying is almost a hallmark of Functional Programming (FP) languages and libraries. However, as my investigations were to discover more about the concept of currying by developing my own, I chose to use the JavaScript (JS) programming without libraries for the following reasons.

  1. JS is not a strict FP language and does not support currying directly (I have to make ne). There are limitations in JS’s recursion (function calling itself) capability but this is more of a performance than functional limitation.
  2. JS is a multi-paradigm language that makes it easier to replicate many FP capabilities. Primarily, JS support first-class and high-order functions (you will need to understand what these are before reading this article).
  3. JS, being multi-paradigm, also supports OOP concepts such as context. Whilst I will not be using context I will be using functions that were designed to facilitate the reuse of methods by switching the context (object) on which it operates.
  4. Finally, like all good languages, JS supports lexical scope (this is limited to Global and Function until ECMAScript 2015 (ES2015) but nevermind that.) This enables closures to be constructed to facilitate the hiding of data and function implementation.

For reasons of compatibility we will employ ECMAScript 5 (ES5) through-out this article but adapting it to ES2015 should be relatively trivial.

So, what is Currying (a layperson’s explanation)

If you are from an OOP or procedural language background you will probably have written functions (and/or methods) that expect multiple arguments. When called all the arguments are expected to be supplied unless default values (such those supported in ES2015) are defined.

Lambda Calculus is a field of mathematics that heavily influenced the development of FP and offers additional capabilities; although one of its characteristics might at first appear to be more of a limitation than a capability. Lambda calculus (LC) defines functions with only one argument. But… LC functions are very often used in combination in order to introduce multiple inputs.

So, Currying is the process of converting a function that expects multiple arguments all at the time of call, into one that expects a series of single arguments and only performs the final calculation/expression when all the required arguments have been supplied.

This is one of two example functions we will be using:

// Calculates the volume of a box given the size of the 3 dimensions
function calcVolume( width, depth, length) {
   return width* depth* length;
}

This is how we would conventionally call the function:

var vol = calcVolume( 3, 4, 5) // result = 60

And this is how the function could be executed in its curried form:

var vol = curriedCalcVolume( 3)( 4)( 5) // same result

At this juncture you might be asking yourself “What is the point?”. Before attempting to answer that question I will first introduce another FP concept called Partial Applications (PA).

A Partial Application (again in layperson’s terms) is a function produced by providing an incomplete set of arguments to a curried function. The PA is a mechanism for binding arguments into the (stack of the) function, making it accessible to the calculating function. As illustrated above, the curried calcVolume function is in fact three functions; two PA’s plus the final ‘calculating’ function (CF).

The process we have goes:

  1. Convert a standard function into a curried function (PA1).
  2. Call PA1 with the value of width creating PA2.
  3. Call PA2 with the value of depth creating CF.
  4. Call CF with the value of length to obtain the calculated result.

The important thing to notice here is that CF is aware of the width and depth before it is passed the length parameter. Another way of viewing this is, PA2 providing the cross-sectional area of the box. This means that, as well as being able to call CF with different values for length we can also generate different (cross-sectional) area functions (CF) for different boxes without calling calcVolume and supplying the width and depth multiple times. In fact, let’s resolve PA1 and PA2 at the same time.

var smallBoxVol = curriedCalcVolume( 3)( 4) // Area CF()
var meduimBoxVol = curriedCalcVolume( 3)( 5) // Area CF()
var squareBoxVol = curriedCalcVolume( 5)( 5) // Area CF()

Now, we have dedicated volume calculating functions generated from the single curried calcVolume function, using Partial Application (PA) functions. Let’s call each of them with the same length of 6.

console.log( smallBoxVol(6)) // 12x 6 = 72
console.log( meduimBoxVol(6)) // 15x 6 = 90
console.log( squareBoxVol(6)) // 25x 6 = 150

We can call the CFs as many times as we like but only have to supply the value that has changed ‘length’. If we have a new cross-section we can use the curried function to create a new PA for it and only have to supply the width and depth once; on creation.

A very simplistic implementation of the Curried function, specifically to perform the above example could be:

var curriedCalcVolume = (function(BF) {
   return function(width) { // PA1
      return function(depth) { // PA2
         return function(length) { // CF
            return BF(width,depth,length);
         };
      };
   };
})(calcVolume); 

Notice how the PA and CF functions are returned, each expecting a single argument. Also notice how the base function (BF) is passed into the call to generate the curried version. The process of currying calcVolume creates PA1, which when called with the width produces PA2, which in-turn produced the CF function when called with the depth value. Finally the CF function calls the original base function (BF) when it is supplied with the length.

If you are particularly interested in the distinction between curry and partial application functions, I suggest you check out an article at (2)ality by Dr. Axel Rauschmayer.

Variable arguments

In the next part of this article I will describe an implementation of the Curry function that supports the above example. However, the implementation will, from the outset, also support the provision of multiple arguments (which is rather at odds with the discussion so far.)

We will go developing the Curry function so it will continue to accept an unspecified number of arguments until a terminal indicator (no argument) is supplied. This capability is about as far from Lambda Calculus as you can go but it is very useful for processing lists of values of unknown length.

Consider the implementation of an averaging (mean) function in a spreadsheet. We cannot stipulate there must be a fixed number of values so be can define our function interface. We either need to accept a single list of values or be flexible in the number of arguments we accept. Yet, we cannot implement a function for each variation on the number of arguments we want to average. There is also the potential added complexity that we might not be able to access all the values to be averaged at the same time.

Here is the scenario for the variable number of arguments: consider logging the progress of a process and reporting the results at the end. The curried function will create PAs that expect a string to append to a log. The CF will result from calling a PA without an argument, at which point the log will be output to the console.

In this example the CF is a PA until it is called without a string (terminal indicator) for adding to the log.

Part One: The “apply” approach

My first Curry function uses the JS apply function method to invoke the CF after building up an array of argument values.

The first thing to try is trivial; call the curry function with a function and return a function back.

function curry( fn) { return fn; }

Test:

var curriedCalcVolume = curry( calcVolume);
console.log( curriedCalcVolume( 3, 4, 5)); // 60

Step two; return a function to accept the arguments and call the CF with them, forming a ‘closure’:

function curry( fn) {
   return function() {
      return fn.apply( null, [].slice.call( arguments));
   };
}

Test:

var curriedCalcVolume = curry( calcVolume);
console.log( curriedCalcVolume( 3, 4, 5)); // 60

Next, we will build up an array inside the outer function of the closure and only invoke the BF when we have all the required arguments. Until then we will return a (PA) function to collect all but the last argument. The CF will take the last argument and process the BF using all the supplied arguments.

function curry( fn) {
   var args = [];
   return function _pa() {
      args= args.concat( [].slice.call(arguments));
      return (args.length == fn.length)?
         fn.apply( null, args): // CF => BF
         _pa;
   };
}

Test:

var curriedCalcVolume = curry( calcVolume);
var smallBoxVol = curriedCalcVolume( 3)( 4);
console.log( smallBoxVol( 5)); // 60

All good so far, but we have a problem when we try to reuse the curry function (PA1) to create the mediumBoxVol and largeBoxVol partial applications.

var meduimBoxVol = curriedCalcVolume( 3)( 5);
var squareBoxVol = curriedCalcVolume( 5)( 5);

console.log( meduimBoxVol(6)); // _pa function
console.log( squareBoxVol(6)); // _pa function

Instead of the expected values (90 and 150 respectively) we are returned the _pa function. This because the args array is already saturated. We could change the relative length check from an equals to a ‘greater-than or equals to’ comparison but this does not resolve the issue and only goes to generate a ‘function expected’ exception.

We have to re-apply the curry function to generate each new PA, which is better done in line.

var meduimBoxVol = curry( calcVolume)( 3)( 5);
var squareBoxVol = curry( calcVolume)( 5)( 5);

Now the following test produces the desired results.

console.log( meduimBoxVol(6)); // (3x 5) x6 = 90
console.log( squareBoxVol(6)); // (5x 5) x6 = 150

Variable arguments

The above implementation allows PA1 to create CF without the need for PA2 because it appends all arguments that are supplied to the internal array. Thus,

var smallBoxVol = curriedCalcVolume( 3)( 4)

Could be called like:

var smallBoxVol = curriedCalcVolume( 3, 4)

This is not entirely consistent with Lambda Calculus but it is a very useful and efficient feature of the above implementation.

Terminal indicator

What if we want to remain flexible over the number of arguments a curried function can use; such as when a base function processes an array. Let us consider a second example: build a log of progress messages and output the entire log at the end of the process.

// Compile progress log and issue consolidated report
function recordProgressLog() {
   var arrLog = [].slice.call(arguments);
   return "\t"+ arrLog.join("\n\t");
}

The direct calling of the function,

var recordProgress= recordProgressLog( 'Alpha', 'Bravo', 'Charlie', 'Delta');
console.log( 'Report:\n'+ recordProgress);

Produces the following output.

Report:
   Alpha
   Bravo
   Charlie
   Delta

We can enhance the original curry function so when it is passed a base function that has no arguments stipulated, the calculation is performed only when the partial application is called without an argument. Until then, arguments passed in via partial applications are accumulated within the curry function as an array.

function curry(fn) {
   var args= [];
   // cache the number of argument the base function expects
   var argc= fn.length;
   return function _pa() {
      args = args.concat([].slice.call(arguments));
      return (args.length >= fn.length) ||
         (!fn.length && !arguments.length)?
            fn.apply( null, args):
            _pa;
   };
}

Now, the following test produced the same results:

var recordProgress= curry(recordProgressLog)('Alpha')('Bravo')('Charlie')('Delta');
console.log( 'Report:\n'+ recordProgress());

We can also break-out the individual partial applications as follows:

var recordProgress= curry(recordProgressLog);
recordProgress = recordProgress('Alpha');
recordProgress = recordProgress('Bravo');
recordProgress = recordProgress('Charlie');
recordProgress = recordProgress('Delta');

console.log( 'Report:\n'+ recordProgress()); // Same result.

Part Two: The “bind” approach

One of the interesting capabilities of the function object’s bind method is its ability to operate in a curry-like mode (see the MDN article on the ‘bind’ method.)

It is possible to resolve the Box volume example using the bind method directly, as follows.

var smallBoxArea = calcVolume.bind( null, 3);
var smallBoxVol = smallBoxArea.bind( null, 4); // 3x 4 = 12
console.log( smallBoxVol( 5)); // 12x 5 = 60

Alternatively, we can replace the first bind method with a call to the following functions:

function curry(fn) { return fn.bind( null); }
function _curry( fn) {
   return function( arg) {
      return fn.bind( null, arg);
   };
}

Replace the second bind method with just a call to the _curry function (above).

var smallBoxArea = _curry( curry( calcVolume))( 3);
var smallBoxVol = _curry( smallBoxArea)( 4); // 3x 4 =12
console.log( smallBoxVol( 5)); // 12x 5 = 60

However, these functions are ugly and can be improved significantly.

function curry(fn, args) {
   return (arguments.length ===1)?
      _curry( fn.bind( null)): fn.bind( null, args);
}
function _curry(fn) {
   return function(arg) {
      return curry( fn, arg);
   };
} 

This gives us:

var smallBoxArea = curry( calcVolume))( 3);
var smallBoxVol = _curry( smallBoxArea)( 4); // 3x 4 =12
console.log( smallBoxVol( 5)); // 12x 5 = 60

But we can do better by combining these functions into a closure.

Fixed arguments

The added function call makes the Bind version 1 line longer than the equivalent Apply implementation.

function curry( fn) {
   var argc=(fn.length -1);
   return function _curry( fn_) {
      return function( arg) {
         return --argc?
            _curry(fn_.bind( null, arg)):
            fn_.bind( null, arg);
      };
   }( fn);
}

Now we can call them like this:

var smallBoxArea = curry( calcVolume)( 3);
var smallBoxVol = smallBoxArea( 4); // 3x 4 =12
console.log( smallBoxVol( 5)); // 12x 5 = 60

A single call to curry the original function with partial applications returned until the last argument returns the result of the calculation.

A benefit the Bind approach has over the Apply Curry function is the reduced retention of state. Whilst the number of arguments the base function expects is calculated and reduced as they are supplied, none of the arguments supplied are accumulated within the function. Instead, the arguments are consolidated within the call stack and released, in order, when the base function is finally called. However, the Bind version does not permit to supply of multiple arguments, which is at least in keeping with the LC principles.

Terminal indicator

The function we need, using bind, looks like this:

function curry(fn) {
   return function _curry(fn_) {
      return function(arg) {
         return arguments.length?
            _curry(fn_.bind(null,arg)):
            fn_();
      };
   }(fn);
} 

Re-using the second example test function;

var recordProgress= curry(recordProgressLog);
recordProgress = recordProgress('Alpha');
recordProgress = recordProgress('Bravo');
recordProgress = recordProgress('Charlie');
recordProgress = recordProgress('Delta');

console.log( 'Report:\n'+ recordProgress());

returns the desired output:

Report:
   Alpha
   Bravo
   Charlie
   Delta

Now to amalgamate the two bind implementations.

function curry( fn) {
   var argc=(fn.length -1);
   return function _curry( fn_) {
      return function( arg) {
         return !(--argc)?
            fn_.bind( null, arg):
            (!arguments.length?
               fn_(): _curry(fn_.bind(null,arg)));
      };
   }(fn);
}

In this implementation, when the base function is not expecting arguments the value of argc starts at -1 and reduces by the number of parameters supplied, but the value will always be less than zero and therefor ‘truthy’. 

Conclusion

It is possible to construct a curry function in as few as a dozen lines of code. In fact we have produced two examples above that both satisfy the key requirements of generating partial applications (PA) to collect arguments and perform the base function when sufficient arguments have been provided.

We have even been able to facilitate the currying of functions that have a variable number of arguments by calling the PA without a value to trigger performance of the base function. Our two solutions, however, offer different fringe benefits.

The Apply-based solution is capable of consuming more than one argument in a single PA call, which offers performance and code simplification benefits at the cost of stretching the Lambda Calculus rules and maintaining some internal state.

The Bind-based solution remains strict in its acceptance of arguments one at a time but does not (obviously) maintain state (as it is hidden within the call stack.)

Whichever we choose to employ it we would be well advised to ensure the base function remains as pure as possible, only using data supplied through arguments and returning a single result.

I suspect it would be relatively trivial to produce a Call-based solution if we were willing to accept the consolidated arguments as an array into the base function, but this is such an edge case it is unlikely to be as useful as the two approaches we have developed in this article.

Remain unconvinced

If you continue to fail to see what practicable use currying could be for your JavaScript, hopefully the following case might convince you. This is taken from the YouTube video from FunFunFunction on Currying.

Example

Consider the Array map() method that came in with ES5. Instead of using a for or while loop to traverse each item in an array and pass it to a function for processing, you can pass the function into the map method. But what if you also want to pass the function additional parameters such as a multiplying factor on an array of numbers? We could use a closure, using ES6 arrow functions, to curry the multiplying function so we can supply the factor early:

const multiplier= factor =>
   item => (factor * item);
let sourceArray=  [1,2,3];
let resultArray= sourceArray.map( multiplier(10));  // [10,20,30]
console.log( sourceArray, "x10=", resultArray);

Resources

The primary products from this article can be found in my ApplyBind Curry function GitHub repository.