Description
Warning: This is a long ticket, but the short version of it is that: _(someObject)._map(mapFunc).compact(); // == awesome!
Currently if one wants to combine two Underscore functions (eg. to compact the results of a map operation) one has to either use multiple `_()` calls, or else use `.chain()` and `.value()`:
_(someObject).chain().map(mapFunc).compact().value();
_(_(someObject).map(mapFunc)).compact();
Now both of those are great, but in a perfect world we'd be able to express the same idea less verbosely. After all, Underscore uses map
, not mapABunchOfObjectsInToANewBunchOfObjects
right? But there's a reason for the current syntax: you need to specify "start chaining here" and "stop chaining here" ... or do you?
What if there was a way to say "start chaining here, and stop chaining after the next method? Something like:
_(someObject).chainNext().map(mapFunc).compact();
That's a little better, but it doesn't look so great if we have more chained functions :
_(someObject).chainNext().map(mapFunc).chainNext().compact().flatten();
But what if we could tell Underscore to start chaining as part of that first map call? In other words:
_(someObject).chainMap(mapFunc).compact();
_(someObject).chainMap(mapFunc).chainCompact().flatten();
That's the right idea, but it's still doesn't look very Underscore-y to me ... what if we tweaked the syntax to look more like the original .. :
_(someObject)._map(mapFunc).compact();
_(someObject)._map(mapFunc)._compact().flatten();
Now that, is just awesome, if I do say so myself. Just for comparison purposes:
_(someObject).chain().map(mapFunc).compact().value(); // Old Style #1
_(_(someObject).map(mapFunc)).compact(); // Old Style #2
_(someObject)._map(mapFunc).compact(); // New Style
_(someObject).chain().map(mapFunc).compact().flatten().value(); // Old Style #1
_( _(_(someObject).map(mapFunc)).compact()).flatten(); // Old Style #2
_(someObject)._map(mapFunc)._compact().flatten(); // New Style
As you can see, we get the joy of chaining without the need for special extra calls or LISP-like parenthesis usage. Oh, and did I mention that all of this can be achieved, with 100% backwards compatibility, in about 10 lines of code?
var chainingMixin = _(_).chain().methods().reduce(function(memo, methodName) {
if (methodName == 'chain') return memo;
memo['_' + methodName] = function() {
return _(_[methodName].apply(this, arguments));
};
return memo;
}, {}).value();
_.mixin(chainingMixin);
// The irony of the fact that I have to use chain to get rid of chain is not lost on me
So, in summary:
- as expressive (if not more so) as the current syntax (readability win)
- <10 already-written lines (easy to implement: win)
- shorter syntax (code golf win)
- 100% backwards compatible (no one is cheesed-off win)
P.S. Currently I don't have any unit tests or documentation written, but if this is something the Underscore team wants I'll be more than happy to provide a pull request with both (I just don't want to bother if the core concept is disliked).
P.P.S. I made a somewhat similar request in #583, but unlike that request this one is 100% backwards compatible.