Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative Chaining Syntax #933

Closed
machineghost opened this issue Jan 8, 2013 · 7 comments
Closed

Alternative Chaining Syntax #933

machineghost opened this issue Jan 8, 2013 · 7 comments

Comments

@machineghost
Copy link

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.

@michaelficarra
Copy link
Collaborator

@machineghost: You can edit your own comments. Just be courteous and note at the bottom that you edited it.

@vendethiel
Copy link

Not sure the underscore should be at the beginning - it usually means protected/private

@machineghost
Copy link
Author

@michaelficarra Thanks for the tip; I've edited my post (and deleted my comment).

@Nami-Doc That's a good point. I was trying to make the syntax match existing the Underscore style as much as possible, so I put the underscore at the beginning to emulate the underscore in _(foo).

But you are 100% correct: underscore-prefixed methods are frequently interpreted as being private (or "pseudo-private" if you want to get technical). So just for fun I tried putting the Underscore at the end:

_(_(_(someObject).map(mapFunc)).compact()).flatten(); // Old Style
    _(someObject)._map(mapFunc)._compact().flatten(); // Underscore Before
    _(someObject).map_(mapFunc).compact_().flatten(); // Underscore After

and in fact I think it works very well! If you break it down it sort of "reads" correctly:

.map_(mapFunc) // breaks down to ....

.map          // do a map
_             // underscore-wrap the result
(mapFunc)     // use "mapFunc" to do that map we just mentioned

So personally I'd be happy with either syntax (and personally I'm going to switch to the underscore-after style myself).

@vendethiel
Copy link

Another option might be to add a underscore property which is a clone of base underscore, only returning _() for each function.

_(a)._.map...

Not very readable tho. would go the post-_ syntax

@jashkenas
Copy link
Owner

Very, very cool idea ... but I think that both the confusion with private naming conventions and the slightness of the code advantage you're going to get make it a bit of a golf-stroke-too-far for core Underscore. In terms of clarity, note that you can use chain directly, without having to wrap first, so, in:

_.chain(object).map(mapFunc).reduce(reduceFunc).compact().flatten().value();
_(object).map_(mapFunc).reduce_(reduceFunc).compact_().flatten();

... I'd prefer the former. The absence of underscores-but-not-for-the-last-function, and the mirror of chain/value on the ends help keep things clear.

Edit: Especially since this can be added non-destructively, I do think this would make for a fabulous plugin. Let me know if you end up packaging one up, and I'll spread the word.

@machineghost
Copy link
Author

As suggested this mix-in is now available as "Underscore Grease": https://github.com/machineghost/Underscore-Grease. Thanks in advance for any publicity.

@jashkenas
Copy link
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants