Notice that our function factory example's bindFirstArg()
and bindSecondArg()
functions only work for functions that have exactly two arguments. We could write new ones that work for different numbers of arguments, but that would work away from our model of generalization.
What we need is partial application.
Partial application is the process of binding values to one or more arguments of a function that returns a partially-applied function that accepts the remaining, unbound arguments.
Unlike the bind()
function and other built-in methods of the Function
object, we'll have to create our own functions for partial application and currying. There are two distinct ways to do this.
var partial = function(func){...
Function.prototype.partial = function(){...
Polyfills are used to augment prototypes with new functions and will allow us to call our new functions as methods of the function that we want to partially apply. Just like this: myfunction.partial(arg1, arg2, …);
Here's where JavaScript's apply()
and call()
utilities become useful for us. Let's look at a possible polyfill for the Function object:
Function.prototype.partialApply = function(){ var func = this; args = Array.prototype.slice.call(arguments); return function(){ return func.apply(this, args.concat( Array.prototype.slice.call(arguments) )); }; };
As you can see, it works by slicing the arguments
special variable.
Every function has a special local variable called arguments
that is an array-like object of the arguments passed to it. It's technically not an array. Therefore it does not have any of the Array methods such as slice
and forEach
. That's why we need to use Array's slice.call
method to slice the arguments.
And now let's see what happens when we use it in an example. This time, let's get away from the math and go for something a little more useful. We'll create a little application that converts numbers to hexadecimal values.
function nums2hex() { function componentToHex(component) { var hex = component.toString(16); // make sure the return value is 2 digits, i.e. 0c or 12 if (hex.length == 1) { return "0" + hex; } else { return hex; } } return Array.prototype.map.call(arguments, componentToHex).join(''); } // the function works on any number of inputs console.log(nums2hex()); // '' console.log(nums2hex(100,200)); // '64c8' console.log(nums2hex(100, 200, 255, 0, 123)); // '64c8ff007b' // but we can use the partial function to partially apply // arguments, such as the OUI of a mac address var myOUI = 123; var getMacAddress = nums2hex.partialApply(myOUI); console.log(getMacAddress()); // '7b' console.log(getMacAddress(100, 200, 2, 123, 66, 0, 1)); // '7b64c8027b420001' // or we can convert rgb values of red only to hexadecimal var shadesOfRed = nums2hex.partialApply(255); console.log(shadesOfRed(123, 0)); // 'ff7b00' console.log(shadesOfRed(100, 200)); // 'ff64c8'
This example shows that we can partially apply arguments to a generic function and get a new function in return. This first example is left-to-right, which means that we can only partially apply the first, left-most arguments.
In order to apply arguments from the right, we can define another polyfill.
Function.prototype.partialApplyRight = function(){ var func = this; args = Array.prototype.slice.call(arguments); return function(){ return func.apply( this, [].slice.call(arguments, 0) .concat(args)); }; }; var shadesOfBlue = nums2hex.partialApplyRight(255); console.log(shadesOfBlue(123, 0)); // '7b00ff' console.log(shadesOfBlue(100, 200)); // '64c8ff' var someShadesOfGreen = nums2hex.partialApplyRight(255, 0); console.log(shadesOfGreen(123)); // '7bff00' console.log(shadesOfGreen(100)); // '64ff00'
Partial application has allowed us to take a very generic function and extract more specific functions out of it. But the biggest flaw in this method is that the way in which the arguments are passed, as in how many and in what order, can be ambiguous. And ambiguity is never a good thing in programming. There's a better way to do this: currying.