-
Notifications
You must be signed in to change notification settings - Fork 2k
Low-precedence method call syntax for paren-free chaining #2114
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
Comments
👍 |
2 similar comments
👍 |
👍 |
To me, the "before" actually looks a lot better and is more readable. The argument of "making the same things look the same" breaks down because now there are two different syntaxes for calling a function |
👍 |
-1 This feature adds complexity to the language, and it's obviously not essential. The ".." syntax feels very cryptic to me, and the only alternative I can think of is, um, parentheses. |
👎, chainable APIs sucks. https://gist.github.com/1730755 |
Hey folks -- although I've said this before in other tickets ... simply 👍-ing a post doesn't help get features added to CoffeeScript, because the sad reality of Github issues is that they aren't a democracy. If you think this feature is a good idea, tell us why. If you think it could be improved, tell us how. Or if you think that @avdi's proposal is 100% complete and perfect as it stands, tell us that. ;) |
good point, @jashkenas. I like the idea of avoiding all parens for methods and I would expect a method chain to be evaluated from left to right just as in Ruby, Obj-C etc., I think the two-dots is a bit cryptic as well, and it looks a bit too much as |
I like this proposal a lot more than of the over-engineered proposes of #1495 Correct me if I'm wrong, can this operator can be described as:
Cannot wait to be able to do |
@yuchi it certainly is |
The first time I wrote a jQuery selector in CoffeeScript I tried to write: $ "a:first-child".click (event) -> While it's hard to tell what @avdi's suggested syntax could cause in the long run, it has one clear disadvantage: not being intuitive at all. |
Won't we still need parenthesis for function calls that don't have arguments? If this proposal wen't through, someone might expect this to work: $ '#selector' .. hide .. Turning into this: $('#selector').hide() |
@rubymaverick: that's another very surprising thing about CoffeeScript syntax, and one which I hope will be addressed some day. But that's a discussion for another ticket; and one which I rather imagine you've all talked to death by now :-) |
@yuchi: hah, the |
Coco: if someone were ever to mean e; also, no-one?? will expect |
There are some things I like about this idea, but there was something in your justification that struck me as out of place:
Wouldn't the two dots (or whatever) still signal that there was something "special" about that call? It seems like there is the potential to replace one possible confusion with a greater one, since we are now introducing another method call syntax. |
@zenhob: The difference I am concerned with in this particular case is false differences signaled within a single line of code. To better illustrate, here's a more egregious example: name = $('.foo').closest('form').find(':file').attr 'name' In that example, Compare:
In this example there is exact syntactical parity for each call in the chain (and fewer parens FWIW). Yes, this may differ from other lines in which the single |
What about using $: "#some_selector" click: (e) -> The only problem is the ambiguity with object literals, so this wouldn't work: $: "#some_selector" css: display: "block", "font-style": "italic" because it'd be impossible to tell whether that should compile to $: "#some_selector" css: {display: "block", "font-style": "italic"} |
Counterpoint:
Compare:
The more I look at this the less I think it's needed. |
This issue has actually come up before in #1407 (also #1495, at least the first part), and discussion floated around the same sort of proposal: $ '.foo' > closest 'table' > find 'tr' > each (i, tr) ->
$ '.foo' .. closest 'table' .. find 'tr' .. each (i, tr) ->
$ '.foo' ... closest 'table' ... find 'tr' ... each (i, tr) -> This syntax also came up: $ '.foo' .closest 'table' .find 'tr' .each (i, tr) -> with the possible surprise that these would not be equivalent: get words .split ' ' .filter (w) -> r.test w .join(', ')
get words .split ' ' .filter((w) -> r.test w).join(', ') There are obviously pros and cons to both approaches. I'm not a fan of adding an operator to CoffeeScript that doesn't have an equivalent in JavaScript, although it could be argued that CoffeeScript certainly has syntax constructs that are not valid JavaScript, so what would be the harm of adding a operator that is not valid JavaScript either. I'm also not a fan of the "missing parentheses" approach because it seems too magic to me, although again it could be argued that CoffeeScript already lets you omit parentheses, so filling them in for you when you chain methods isn't that far of a stretch (there is also a multi-line chaining syntax in this case to consider, although that is another topic). That said, I think @avdi's argument is a good one. It's not that there are two different ways to say the same thing in CoffeeScript -- it's that it's possible to write code that looks like it does something different when compared with a differently written piece of code, when in fact it doesn't. I totally get this -- I really dislike languages where how they look doesn't match how they work. So just for that, I'm sold on fixing this issue. As to which solution to go with exactly, as I mentioned I would prefer $ '.foo' .closest 'table' .find 'tr' .each (i, tr) -> because it seems like could be easily consistent with multi-line chaining, but that is just me. |
I'm sure this proposal is dead in the water. Algol got a lot of things wrong, but not parentheses. Parentheses are actually a great invention. I've never seen such ugly code in my life until this issue. Grossssssssssssssssss. |
How would you paren-free these with this proposal?
|
Hmm. If you wanted to paren-free either of those, Interesting... so here's a full-on experiment:
Going completely paren-free, it's admittedly kind of a Magic Eye experience:
And the other approach:
Opinion: the Not saying I'd use it, but it's interesting, anyway. Thoughts, @avdi? |
Parens can be gross but in this case of chaining methods together it feels "right" and using
or if you fancy a C++ approach
It would introduce another non-word character back into the mix but I think it makes more since than |
@chadhietala That proposal is even worse IMO because
|
Ah @erisdiscord yes you are correct. Forgot & is a bitwise "AND" in JS. hmmm :-\ |
I understand where you're coming from, @avdi. I obsess over this myself. I've found piece of mind by more or less always using parens when invoking jQuery methods. If consistency is what you're after, the solution is… name = $('.foo').closest('form').find(':file').attr('name') not… name = $ '.foo' .. closest 'form' .. find ':file' .. attr 'name' To further @paulmillr's point, it's worth noting that method chaining is not necessarily a practice we should encourage. In a Hacker News thread, @raganwald commented:
|
Attribute access via (Edit: looks like mcmire said some of this) In general though, I think this leads to too much ambiguity. Implicit parens can be arbitrarily nested in CoffeeScript, across multiple levels of indentation, across function definitions, etc., and I’d guess it’s often hard to figure out just how many levels of them were supposed to be closed. Some examples: Does Does Does What happens if we add some indentation?
or maybe
etc. |
Unfortunately that solution doesn't address the original example, in which using consistent parenthesis means losing the advantages of significant whitespace to delimit a code block.
There are two distinct kinds of chaining. Writing methods for K-combinator-style chaining, in which the method is called for its side-effect, the result is ignored, and the original receiver returned, is indeed a prop for a language deficiency. Smalltalk, notoriously stingy with its syntax, dedicates an operator to this idiom (called "cascading" in Smalltalk). rectangle
height: 71;
width: 42 On the other hand you have functional chaining, where you successively call methods on the result values of preceding methods in order to progressively transform the results. For instance, in Ruby we might generate a "slug" with: title.strip.downcase.tr_s('^[a-z0-9]', '-') I don't think there's any way around this type of chaining in an OO language, and I'm not sure there's any reason to want to avoid it. I think it may be especially easy to confuse these two types of chaining in JavaScript, since we're all familiar with jQuery, which freely intermixes the two styles. E.g. My second example: $('.foo').closest('form').find(':file').attr('name') Is purely functional, and I'm not sure how avoiding chaining would improve it in any way. I mean, we could reformat it to use a Lisp functional composition style: (attr (find (closest ($ '.foo') 'form') ':file') 'name') But I'm not sure that's an improvement. In fact, having the composition go the other way 'round is one of the little pleasentries of OO programming, since many of us have an easier time reading the code when the order of transformations goes from left to right.In fact, several modern functional languages provide the ability to switch the visual order of function transformation to something closer to the CoffeeScript version for just that reason. TL;DR: there are two kinds of chaining; only one of them is indicative of a missing language feature. |
@avdi: You're forgetting that you could also write it in a left-to-right way like this:
|
Great comment, @avdi. Before reading it I didn't really differentiate the two kinds of jQuery method. I agree that chaining is very natural for |
@jrus I was going to put a bunch of examples here, but suffice it to say that I think ".." should actually take pretty high precedence -- the same as ".", actually. In other words, I don't think it should "jump" out of functions, assignment expressions, etc. A low-precedence operator doesn't make sense to me... that's just my 2 cents. |
I'm pretty sensitive to parens, but they're a sometimes necessary aesthetic blemish. The proposed:
may be no problem for CoffeeScript, but I don't parse it as fluently as I think CoffeeScript strikes a good balance between reducing syntax noise and enhancing readability. Semicolons are superfluous because we find line breaks to be natural delimiters. It's logical that CoffeeScript eliminates them since they serve little purpose (for us). However, the terse/opaque nature of programming languages and APIs works against us for mentally delimiting subexpressions. This proposal would be (mentally) really easy to lex and really hard to parse ;) |
To be honest, I don't see the need for a new "syntax" for it as such, just a kind of detection to see if the next value after a space would work as a stand-alone value. For example... a "b" .c // a("b").c
a "b" [c] // a("b")[c]
a b "c" // a(b(c))
a "b" // a("b")
a "b" ("c") // a("b")("c") Also, it's a little off topic, but the way it detects arguments could be changed, so that the following is true a "b" "c" // a("b", "c") - currently creates an error
a b c // a(b(c)) |
@TrevorBurnham That's basically what I was trying to say. |
Closing in favor of #1407, in conjunction with @raganwald's proposal for indentation-based chaining and closing of open calls ... both of which accomplish the same goal more elegantly than a new keyword. |
Inspired by this discussion, where @jashkenas suggested I file a ticket for it.
Synopsis
Before:
After:
Details
Add a new syntax for calling methods with low precedence, suitable for chaining. In the example above I use
..
, but that's just a suggestion off the top of my head; it could be anything.Why?
As I'm finally getting a chance to use CoffeeScript for real, I've been immediately struck by two things: the language and conventions strive to avoid extra parenthesis (yay!) but in many common cases that's not completely possible (boo!). As a result, I see a lot of code like the "before" example which mixes paren and paren-free calling. Since I strive for code in which "same things look the same", this was a bit jarring. When I first saw the code above it signaled to me that there was something "special" about the call to jQuery's
$()
method, since it required parens and the call to.click
did not. As it turns out the only thing special about is was that it was not at the end of the line.The suggested change would enable even more put-the-top-down-and-let-the-breeze-blow-through-your-hair paren-free coding, which I think would be a Good Thing.
The text was updated successfully, but these errors were encountered: