-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Improve chaining syntax #1495
Comments
I think this is a duplicate of #1251 |
Yes and no. The problem is the same, but proposed solution is different. 1251 was rejected and closed because the syntax proposed ultimately didn't actually improve matters. I feel the above proposals do, in fact, produce a nicer syntax for "train wreck" expressions. It requires you to use separate lines and indentation, but if you wanted to do it on one line, you could still fall back to the existing syntax. I've realized the ".." syntax can be futher improved to
Which I feel looks a lot more like coffeescript than
|
Also see #1407. |
How about just putting the dots there as usual, but instead of this code:
compiling to this JS as it does now:
it would compile as expected to this JS:
This would essentially make the newline character mean something after function calls without parenthesis. |
@devongovett cute, but wouldn't that syntax make it harder to correctly parse this valid (and pretty common) construct?
|
@erisdiscord no, I don't think so as the dots before the function names would be required and there aren't any colons after them. It's really the same as if you just removed parenthesis from around the arguments of each function. |
Whilst my preference for as little punctuation as possible is marked, I can't see anything unambiguous with Devon's rule. |
+1 for @ devongovett's proposal — this might be the behavior I'd expect anyway. |
Y'all should take a look at #1407. The proposal there would allow you to write
without making newlines/indentation significant. In my view, it's the most elegant possible solution to this long-standing problem. |
A more explicit way perhaps could be
or even with surrounding spaces
currently writing all the () parenthesis is a pain. I don't like the idea of using straight indentations for chaining. Indentations to me means "Sub/Below" but chaining is more like "Next"... Something like
would make more 'sense' but Jeremy already wrote that off here. Maybe something like:
could work. I kinda like JulianBirch idea for
|
Just to throw another syntax in the ring, the ellipsis operator could be overloaded to mean "continue with result of last line", as such:
You're not really saving punctuation but it is very simple to write, just as easy to read. |
As qrgnote points out, jashkenas doesn't like the leading dots proposal. Satyr does, obviously, since it works in Coco. Personally, I think* that when writing normal javascript, it's more common to be writing ")." or "})." at the start of a line than just ".", which makes me favour changing the behaviour. I don't think using "greater than" works, because it's impossible to tell what you meant by ">" anymore. Which actually pretty much leaves us with the ".." proposal. *I remember than when Python introduced their inline if statement, they designed it on the basis of analysing a large code base (their own) to see what was the more common idiom. I did a quick scan of jQuery edge for ^\s+[.] which found only one case in which the "dot on newline implicitly closes a bracket" would not have been the intended behaviour. For reference, the line was
|
It'll be great if it can work inline as well... because going back and adding (&) is annoying.
could return
Edit: Actually in practice entering
|
Why fix something that is not broken? Introducing new meaning to existing symbols and operators is risky as at some point the JavaScript itself could decide to overload them. And chaining calls is not a common idiom in either CS or JS. It's just something that one popular client-side library happens to use. Ruby is fine without chained call support. JS and CoffeScript offer you working solutions that require you to be verbose. If you need something else, you can probably roll your own solution without modifying the language, see this Ruby example for an idea of what to add to jQuery's prototype: |
Underscore isn't the only popular library that uses method chaining. jQuery uses it as well. The reason I suggested the ".." syntax is because that's the syntax Clojure has for this, so it's useful in Java as well. |
@JulianBirch, I agree but the idiom is still not part of the language philosophy per se and reusing operators that are already part of the language comes with the risk of them suddenly gaining a meaning in a future version of JS. |
Certainly, and tbh I think this has come down to a "won't change". I do find thrush operators useful, however, and it's a pity CoffeeScript doesn't have one. |
this is exactly what I'm looking and expecting for. |
yes, CoffeeScript does not support chaining. You can't use CoffeeScript to chain, you have to resort to JavaScript. CoffeeScript does such a great job of relieving parenthesesitis, that when I have to return to JavaScript awk!!! Especially when I have to "go back" to add parenthesis. it's not easy to add parentheses backwards
It seems that not supporting chaining is an oversight of CoffeeScript. Chaining isn't just used by jQuery like the previous example shows, it's pervasive in CommonJS modules loading, and what's more Node, then that? Let alone a lot libraries like Underscore supports "chaining" as well. Like mongodb/mongoskin
jQuery and all the custom libraries built off of jQuery is important to support native imho. Parentheses sucks. I don't want to write JavaScript, I want to write CoffeeScript. Question, why was "parentheses" made optional on function/method calls in the first place? That same rationale should be why chaining should be supported without parentheses.
Simple, CoffeeScript doesn't support chaining. |
Consider me insane but I find
If parentheses are your number one problem in programming then let me congratulate you for having nothing else to worry about :) |
Wait, what?
Huh? This looks like CoffeeScript to me: While I agree that a syntax that allows us to leave off parentheses would be the ant's pants, the status quo really isn't as bad as you make it sound. We've been using parentheses for years in other languages. C: (If you're allergic to parentheses, you'll want to be extra careful and avoid exposure to Lisp or S-expressions) |
I think the latter would make my day better :) |
Food for thought from @raganwald: http://news.ycombinator.com/item?id=3175028 |
The substance of my comment onHN: I would use indentation to discriminate between the cases:
For pipelining, and:
For what we are calling “chaining.” I use this now as a personal coding style when writing jQuery stuff with JQuery Combinators:
The first “addClass” and “find” are both sent to “added,” “when” is sent to the result of “find”, “removeClass” and the second “addClass” are both sent to the result of “when.” It feels to me very much like how we indent scope and syntactic blocks like “if” or “unless." |
@raganwald 👍 That is precisely how I feel it should be as well, if CoffeeScript is going to have such a syntax. Lately I tend to write my chained method calls on the same indentation level as the object
but I think that indenting it your way makes more sense and is ultimately more consistent. |
yes, but a single line chaining syntax would be nice.
but @patrys point about future-proofing coffee-script is a good point... my vote right now goes for
|
FWIW, Smalltalk implemented chaining in its syntax and was indentation agnostic, so you could write
or:
If you want to go that way, I would avoid Unfortunately, the semantics I would like in an operator conflict with Coffeescript’s goal of compiling to readable JS that corresponds closely to the semantics of the original source. Instead of an operator that means “send and return the receiver,” I would like an operator that means “send to the previous receiver,” which is exactly what the
I would have suggested:
Meaning “send bar to foo, and send bash(something) to foo, and send thenBlitz to foo.” I fear that the compiled code would be confusing, but like the way it reads. It describes to me what the code is doing, not making me think about the implementation of returning the receiver and then sending a message to the result. Like
|
@raganwald +1. This seems like an intuitive, semantic distinction between chaining vs continuation of previous lines. Moved comments about chaining parentheses omission to #1407. |
Just to recap, it looks like we're discussing three problems:
|
A separate proposal. See #1431. |
I think the clearest solution is chaining method calls on the same indentation level: new Thing
.add foo ...
.add bar ... equivalent to: (new Thing).add(foo ...).add(bar ...) |
I think Coco's approach of:
is sufficient for most libraries that use chaining, as well as (I think) quite intuitively looking like what it means: firstCall(argument, argument).method(arg).otherMethod(otherArg); |
@00Davo with the indentation as it is, that looks to me more like var x = firstCall(argument, argument);
x.method(arg);
x.otherMethod(otherArg); which is arguably more useful since it works identically for common use cases like jQuery, Q, &c. and the behaviour is even more useful for cases where you might want to call multiple methods on the result of one function call. |
I'd personally think unindented chains
would have that meaning, with indented chains having the meaning I suggested. So perhaps it's not quite as clear as I thought. Hmm. |
@00Davo see, to me, if you want meaningful indentation in method chaining, it'd have to look like this, which can get messy with long chains: firstCall arg, arg
.method arg
.otherMethod arg I think this is part of the reason it hasn't happened yet. (I'm going to bed now; it's 2am) |
Well, making "same-level" indented chains work like Smalltalk cascading actually doesn't mean they'll work right with Q or with jQuery. For instance: # jQuery
$ 'selector'
.filter predicate
.hide()
# Q
readFile path
.then YAML.parse
.then processReadYamlObject
.nodeify callback Having the chaining syntax compile to equivalently chained methods in JavaScript is, I think, preferable for reasons like those. (Also, libraries like jQuery are explicitly built to be chainable. JavaScript itself doesn't have cascading syntax, so fewer libs are built with that use in mind.) Hmm. What about actually using readFile path
.then YAML.parse
.then processReadYamlObject
.nodeify callback
readFile path
;then YAML.parse
;then processReadYamlObject
;nodeify callback Compiles to: readFile(path).then(YAML.parse).then(processReadYamlObject).nodeify(callback);
_ref = readFile(path);
_ref.then(YAML.parse);
_ref.then(processReadYamlObject);
_ref.nodeify(callback); |
in LESS they use a.button {
&:hover { }
} Compiles to: a.button:hover {
} I propose we replace the dot syntax for chained calls with
(It literally would be
The only other similar syntax I think makes sense is the
|
Parenthesis, or the lack thereof, are one of the best things about coffeescript. changing
to become
In Proposed syntax:
Keep as much the same as possible. Currently, while it is smelly,
Keeping
Ideally, both compile to
|
Implement #1495, Method call chaining
Shouldn't this issue be closed because of 563f14b? |
No, this discussion is about cascades. |
We should decide whether "we got what we wanted". |
I'd say yes, for now at least :). |
As OP, I'd say it looks closed. |
The other issues mentioning cascades seem to have been closed due to being a duplicate for this. So I guess this task now has to be considered the canonical request for cascades, rather than just simply chaining as it was originally intended for (or we create a new issue for cascading and don't close it as a duplicate of this). Here's an example of cascading in Dart: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html I think this would be really helpful because some APIs simply don't allow for the existing chaining, Promise being the most obvious one: myPromise
.then ->
someOtherPromiseOp()
.catch ->
console.error 'oh no, this error might have happened in someOtherPromiseOp, not in myPromise!' This would solve the above: myPromise
..then ->
someOtherPromiseOp()
..catch ->
console.error 'we know that myPromise threw, not someOtherPromiseOp' |
Nested cascades in CoffeeScript would be a snap with the .. syntax, but I could see a single . prefix syntax could work along with the indent to promote the same idea.
|
@brendan document.querySelector "#mypanel table tr"
.classList.remove "first-row"
.style
console.log "current background is: ", .background
.background = "red"
.border = "2px solid black"
.appendChild new Element
.innerHTML = "<span>This cell is now red</span>" I'm not sure how I feel about this. The At any rate, I can't disagree with notion that "writing functions to return a certain thing just to cater to how you like to write programs is hacking around a missing language feature" |
Didn't realize that CoffeeScript would interpret
as I guess I'd amend the example you posted to be: document.querySelector "#mypanel table tr"
..classList.remove "first-row" # querySelector result's classList
..style # querySelector result's style
console.log "current background is: ", ..background # style's background
..background = "red" # style's background
..border = "2px solid black" # style's border
..appendChild new Element # querySelector result's appendChild
..innerHTML = "<span>This cell is now red</span>" # appendChild result's innerHTML |
Is this likely to be implemented soon? |
I’m not sure what is even still being debated on this thread. If someone wants to improve chaining from where it stands in v2, please open a new issue with a specific proposal. |
Since chaining is a pretty common feature of Javascript libraries, how about a dedicated syntax for it?
e.g.
could be written
Alternatively, you could go with something insanely LISPy
NB, the semantics I'm proposing would be equivalent to
The text was updated successfully, but these errors were encountered: