I'm working on re-writing the underlying code for many of the standard Underscore.js functions to work on my JavaScript skills and am a bit stuck with the _.every
/_.all
. It appears that in the library itself, the _.every
/_.all
function is written only using the existing _.each
function, but I am being encouraged to write a version using my version of _.reduce
(which already incorporates my version of _.each
). I've provided the code for both functions below.
The first test my _.every
function (see below as well) fails is one where all false values are passed in using the _.identity
function (simply return value entered as argument) as the iterator:
Test:
it('fails for a collection of all-falsy results', function() {
expect(_.every([null, 0, undefined], _.identity)).to.equal(false);
});
I have a few questions as to why my _.every
function is failing the test shown above, along with multiple other tests (e.g.; mixed true/false values, undefined values, etc.):
-When calling the iterator function, do I need to use iterator.call
or iterator.apply
? If so, which do I use and how do I specify arguments?
-What benefit is there to using _.reduce
here rather than just _.each
, especially when the Underscore.js library does not use _.reduce
?
-Why does return need to be called twice, once when calling the _.reduce
function, and once within the anonymous function defined within _.reduce
(I've also wondered this when building functions that utilize the _.map
function)? To me, it seems like I am returning the result of the _.reduce
function, which already is returning something.
_.every:
_.every = function(collection, iterator) {
// TIP: Try re-using reduce() here.
return _.reduce(collection, function(allFound, item) {
return iterator(item) && allFound;
}, true);
};
_.each:
_.each = function(collection, iterator) {
// define spec for arrays
if (Array.isArray(collection)) {
for(var i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
}
// define spec for objects
else {
for(var key in collection) {
iterator(collection[key], key, collection);
}
}
};
_.reduce:
_.reduce = function(collection, iterator, accumulator) {
// add condition to set accumulator if no explicit starting value is given.
if (arguments.length < 3) {
accumulator = collection[0];
}
_.each(collection, function(value) {
accumulator = iterator(accumulator, value);
});
return accumulator;
};
Your test isn't passing because it's not returning false
as expected (though it is returning a falsey value).
_.every = function(collection, iterator) {
return _.reduce(collection, function(allFound, item) {
return iterator(item) && allFound;
}, true);
};
What happens when you return iterator(item) && allFound
is that if iterator(item)
is falsey (but not false
), it will not return false
, but the value of iterator(item)
. To verify this for yourself, open a REPL, and type undefined && true
; the result will be undefined
, not false
.
So if you want this to explicitly return false
, and not just a falsey value, you'll have to coerce it to a boolean. You can do either Boolean(truthy_or_falsey_value)
or !!truthy_or_falsey_value
. I usually prefer the latter, so change your implementation thusly:
_.every = function(collection, iterator) {
return _.reduce(collection, function(allFound, item) {
return !!iterator(item) && allFound;
}, true);
};
Your other questions:
When calling the iterator function, do I need to use iterator.call or iterator.apply? If so, which do I use and how do I specify arguments?
It depends on what your goal is. call
and apply
are primarily used when you want to control the value of the this
keyword in the function body. Some of JavaScript's built-in array methods (like Array.prototype.map
and Array.prototype.filter
) take a thisArg
, which is what is supplied do the callback using call
or apply
. As far as the difference between call
and apply
, it's only how the arguments are handled. See this answer for more details.
What benefit is there to using
reduce
here rather than justeach
, especially when the Underscore.js library does not usereduce
?
Probably none, or very little. There could be a performance difference, but the best way to find out would be to profile both approaches.
Why does return need to be called twice, once when calling the _.reduce function, and once within the anonymous function defined within _.reduce
If you want a function -- any function -- to return a value, you must call return from within that function. You can't expect to call return
from an inner function, and expect the enclosing function to magically understand that it's supposed to, in turn, return the value of the called function. Some languages default to returning the value of the last expression in the function if return
isn't explicitly called, which is either convenient or confusing, depending on your perspective. If you have experience with such a language (Ruby, for example), then all the return
statements may seem a little excessive to you.
As an editorial note, I feel that iterator
is a poor naming choice for the testing function. It is not actually iterating over anything (the function that it's an argument to is doing any iteration). A better name might be the very generic callback
or cb
. The term "predicate" means a function that maps a value to true
or false
, which is my preferred terminology. Another common choice is simply test
, since it is, after all, just a function that's performing a binary filter on its argument.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With