Reducing Redundancy by Using a Partial Application
Problem
You have a function with three arguments (has an arity of three (3)) but the first two arguments are typically repeated based on specific use. You want to eliminate the rep‐ etition of arguments whenever possible.
Solution
Create one function that manipulates three values and returns a result:
function makeString(ldelim, str, rdelim) { return ldelim + str + rdelim; }
Now create another function that accepts two arguments, and returns the previously created function, but this time, encoding two of the arguments:
function quoteString(str) { return makeString("'",str,"'"); } function barString(str) { return makeString("-", str, "-"); } function namedEntity(str) { return makeString("&#", str, ";"); }
Only one argument is needed for the new functions:
console.log(quoteString("apple")); // "'apple'" console.log(barString("apple")); // "-apple-" console.log(namedEntity(169)); // "© - copyright symbol
One function performs a process on a given number of arguments and returns a result, while a second function acts as a function factory: churning out functions that return the first function, but with arguments already encoded. As the solution demonstrates, the encoded arguments can be the same, or different.
A Partial Function Factory
We can reduce the redundancy of our function factory even further by creating a generic function, named partial(), capable of reducing any number of arguments for any number of functions:
function partial( fn /*, args...*/) { var args = [].slice.call( arguments, 1 ); return function() { return fn.apply( this, args.concat( [].slice.call( arguments ) ) ); }; }
We’ll need a copy of the arguments passed to partial() but we don’t want the first, which is the actual function. Typically, to convert a function’s arguments into an array, we’d use syntax like the following:
var args = [].slice.call(arguments);
In partial(), we specify the beginning value for slice(), in this case 1, skipping over the first argument. Next, an anonymous function is returned that consists of returning the results of the apply() method on the function passed as an argument to parti al(), passing in the anonymous function as this, and concatenating the arguments passed to partial() to whatever arguments are also passed to the newly generated function.
The apply() method is similar to call() in that it calls the function (repre‐ sented by this), but accepts an array-like list of arguments, rather than an actual array of arguments. Now we can create functions to generate strings, or add a constant to numbers, or any other type of functionality:
function add(a,b) { return a + b; } var add100 = partial(add, 100); console.log(add100(14)); // 114
However, we have to be aware, of the order of arguments. In the case of the delimited string function, we need to remember that partial() concatenates whatever is passed to the generated function to the end of the argument list passed to partial():
function makeString(ldelim, rdelim, str) { return ldelim + str + rdelim; } var namedEntity = partial(makeString, "&#", ";"); console.log(namedEntity(169));
I had to modify makeString() to expect the inserted string to be at the end of the argument list, rather than in the middle, as was demonstrated in the solution. Using bind() to Partially Provide Arguments ECMAScript 5 simplifies the creation of partial applications via the Function.proto type.bind() method.
The bind() method returns a new function, setting this to whatever is provided as first argument. All the other arguments are prepended to the argument list for the new function.
Rather than having to use partial() to create the named entity function, we can now use bind() to provide the same functionality, passing in undefined as the first argument:
function makeString(ldelim, rdelim, str) { return ldelim + str + rdelim; } var named = makeString.bind(undefined, "&#", ";"); console.log(named(169)); // "©"
No comments:
Post a Comment