Reputation: 27199
The following code is taken from Jon Resig's book Secrets of JavaScript Ninja to explain how to use closures to implement partial application of functions. However I have issues understanding the intent of the variable arg
. Why is it required and how does it simplify the problem in hand of pre-filling some arguments to a function? What can be a possible application of this partial
function?
Function.prototype.partial = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
var arg = 0; // How does this do the currying
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++]; //This line is where the confusion is
}
}
return fn.apply(this, args);
};
};
Edit: I am confused because args
and arguments
are necessarily the same here since after the call args = Array.prototype.slice.call(arguments);
args
is an true array object that holds all the information contained in arguments
. So if something is undefined
in args
, how can we possibly have something inside arguments
?
Upvotes: 3
Views: 199
Reputation: 10536
Ok, I'm trying to explain it piece by piece:
Function.prototype.partial = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
var arg = 0;
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++]; //This line is where the confusion is
}
}
return fn.apply(this, args);
};
};
First line:
var fn = this, args = Array.prototype.slice.call(arguments);
This stores the value of this
and the value of arguments
in two variables, because both values would get overriden in the following function block:
return function() {
//inside here arguments will be whatever is passed to this returned function later.
};
The for loop:
var arg = 0;
for (var i = 0; i < args.length && arg < arguments.length; i++) {
}
It will basically loop through all arguments that were passed to the partial
function and exits earlier if arg >= arguments.length
.
if (args[i] === undefined) {
args[i] = arguments[arg++]; //This line is where the confusion is
}
So, if an argument of the args
array is undefined, we replace it with the next argument in the arguments
array. When every arguments are replaced, the original function gets called with the merged arguments array:
return fn.apply(this, args);
Here is how it would work in practice:
function xy(arg1, arg2) {
console.log(arg1 + " / " + arg2);
}
var p1 = xy.partial("foo", undefined);
p1("bar") //"foo / bar"
var p2 = xy.partial(undefined, "bar");
p2("foo") //"foo / bar"
var p3 = xy.partial("foo");
p3("bar") //"foo / undefined" --> because you have to activly pass "undefined" otherwise the arguments array is too short
And last but not least, how this code workes in detail with the example p1 = xy.partial("foo", undefined);
:
//lets call xy.partial("foo", undefined);
Function.prototype.partial = function() {
//fn = xy
//args = ["foo", undefined]
var fn = this, args = Array.prototype.slice.call(arguments);
//return function that is assigned to p1
//lets call p1("bar")
return function() {
//arguments = ["bar"]
var arg = 0;
//for (var i = 0; i < 2 && arg < 1; i++)
for (var i = 0; i < args.length && arg < arguments.length; i++) {
//first iteration:
//args[0] === "foo" -> nothing happend
//second iteration:
//args[1] === undefined -> args[1] = arguments[0] (= "bar"); arg++;
if (args[i] === undefined) {
args[i] = arguments[arg++]; //This line is where the confusion is
}
}
//at this point: args = ["foo", "bar"];
//now just call the function with the merged array
return fn.apply(this, args);
};
};
Upvotes: 3
Reputation: 2831
Partial function helps you to pre fill the function arguments that is always needed for the function execution. This helps is reducing the overheads in many ways.
The example above helps in filling undefined arguments later.
The arg is a private variable to keep track of the undefined arguments and fill the undefined arguments in sequence.
args[i] = arguments[arg++];
The above line filling the undefined arguments those were left undefined when using partial.
Example:-
function a(b,c){ console.log(b,c); }
//pre filling the second argument with numeric value 1. But first argument is still undefined. This will return a new function.
var g = a.partial(undefined, 1);
//Now when you actually invoke the function and pass it the parameters this will fill the arguments left undefined previously i.e. "b" . In this case the var arg will fill the previous args array position those were undefined i.e. args[0] was undefined. So var arg will read the passed arguments in sequence and fill the previously undefined arguments for you.
g("Hi I was undefined before");
If don't want to args gets permanently change then update the partial implementation as below.
Function.prototype.partial = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
var arg = 0, g;//g will hold the clone of args
g = args.slice(0);//clone the args array and use that instead of original args.
for (var i = 0; i < g.length && arg < arguments.length; i++) {
if (g[i] === undefined) {
g[i] = arguments[arg++];
}
}
return fn.apply(this, g);
};
};
function fn(a, b){ }
var fn = f.partial(undefined, 3), x = fn(2), y= fn(4);//Now this will work
Upvotes: 1
Reputation: 18078
I think this is best explained in words. At least, I'll give it a go :
The outer function gives rise to the outer arguments
(once) and the inner function gives rise to the inner arguments
(once or more times).
Meanwhile args
is an "accumulator" in which parameters of the first (outer) call and those of all subsequent (inner) calls are progressively stored. The line :
args[i] = arguments[arg++];
simply does the accumulating, on a next-to-arrive basis.
At each inner call fn.apply(this, args)
may or may not execute successfully. fn will be written in full knowledge that it will curried. Typically,
undefined
(or null
or false
or -1
)Upvotes: 0