Skip to content

Alternative Chaining Syntax #933

Closed
Closed
@machineghost

Description

@machineghost

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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions