Unnecessary parentheses in function calls.
jussiry opened this issue · 76 comments
Currently foo bar .baz()
compiles to foo(bar.baz());
This seems unlogical, since the same result can be gained with cleaner code by writing foo bar.baz()
.
So should't it rather compile to foo(bar).baz();
? This would allow us to write code with even less parenthesis in many situations (something i'v really learned to love with CoffeeScript).
What compilation would you recommend for a b c .d()
? Should it be a(b(c)).d()
or a(b(c).d())
? Should a .b()
then be compiled as a().b()
?
What about this common use case for jQuery? What would the whitespace do here?
$('a')
.button()
.click(load_ajax)
So you're suggesting a function call's argument list ends when it sees a dot-style property access but only when there's whitespace before the dot (which, I'll remind you, is just JS)?
> obj.f0 a0, a1, a2 .f1 b0.b00, b1 .f2 c0
obj.f0(a0, a1, a2).f1(b0.b00, b1).f2 c0
I can kind of see why someone might want to write that the first way (DSLs maybe?), but it is quite a bit more readable with explicit parentheses. -1 for now.
erisdiscord, i think a b c .d()
should compile to a(b(c)).d()
(property after . binds to the first function).
EDIT: now that i think of it, a .b()
should just compile to a.b()
. Like in CoffeeScript in generals, without parenthesis function is executed only when it's given parameter(s).
michaelficarra, yes, but JS is ugly. ;) I agree that the second example is cleaner and I hope that no one writes the code in real life with as many parameters without parenthesis. But the point is not to allow long lists of parameters without parenthesis (as long as there's a clear logic on how they would compile if someone wrote them), but to allow cleaner code in simpler examples.
Interesting. There would be enough visual hint since you don't usually space before .
, and you can still write a b. c
to mean a(b.c)
. Similar to the new "command chain" in Groovy 1.8.
👍
Increased craziness:
a b c .d()
compiles to
a(b(c).d())
and
a b c .d()
compiles to
a(b(c)).d()
(good luck if you don't like fixed-width fonts :P )
@erisdiscord I'd rather call it "consistent".
Humm... on a related note, what would you think about doing operator precedence using spaces? Something like
1+1 * 4 # 8, not 5
@thejh I call it "pretty difficult to read". I'm not really a fan of the original proposal and I can't see this version bringing any additional value. -1 :C
@thejh, why would it make a difference if there's one or two spaces before dot (.)? As i see it, with this proposal a b c .d()
would also compile to `a(b(c)).d()``.
@thejh, on the operator precedence: i like it. :) Haven't given it that much thought, though.
To return to the jQuery example presented by @erisdiscord:
$('a')
.button()
.click(load_ajax)
Would work just as it now does, but with the new rules you could actually write it without the parenthesis:
$ 'a'
.button()
.click(load_ajax)
Which isn't possible in CoffeeScript at the moment. The only problem would arrive if you would add another function before $:
another_func $ 'a'
.button()
.click(load_ajax)
Because in this case the .button() would be executed on another_func
, but i don't see this being a very common use case.
It gets really unreadable after a few methods. Consider this:
get(words).split(' ').filter((w) -> r.test w).join(', ')
It would be written as
get words .split ' ' .filter (w) -> r.test w .join(', ')
You just borked the function expression. This could be the case for a custom operator, we don't have many options since |
is taken:
get words ..split ' ' ..filter (w) -> r.test w ..join(', ')
$.get '/api' ..success fn ..error fn
// overload then
foo bar then .baz()
get words then .split ' ' then filter (w) -> w then join ', '
// spermatozoid operator
foo bar ~> .baz()
get words ~> split ' ' ~> filter (w) -> w ~> .join ', '
But it doesn't make things much better, besides being a huge departure from "normal" syntax.
One that is kind of ok is re-using @
:
foo bar @ baz()
This would be different from foo bar @baz()
in the same fashion that foo 1 + 2
is not the same as foo 1 +2
. And it would re-use the @
semantics of being this
, in this case this
is the result of the left-hand expression.
For clarity the dot could be kept: get words @.split ' ' @.filter (w) -> w @.join ', '
get words .split ' ' .filter (w) -> /bacon/.test w .join(', ')
You just borked the function expression.
Only because you mixed implicit indentation (inserted after ->
, consuming the rest of the line) which obeys its own, different rules.
One-liner functions must be parenthesized in such cases.
@satyr but that kind of defeats the purpose of not having to nest things in parenthesis.
"Significant whitespace" speaks primarily about significant linebreaks and significant indentation. These are readable. Not all whitespace should be significant. Some types of whitespace should not be significant: such as the whitespace within a line.
Some types of whitespace should not be significant: such as the whitespace within a line.
Already significant beyond help:
$ coffee -bpe 'a? b ? c?'
if (typeof a === "function") {
a(typeof b !== "undefined" && b !== null ? b : typeof c !== "undefined" && c !
== null);
}
$ coffee -bpe 'a[b...] [c...]'
var __slice = Array.prototype.slice;
a.slice(b)(__slice.call(c));
Yeah, we are pretty big on the significant whitespace here. Still, this one's a bit of a stretch. I could live with it, but it's nowhere near as intuitive or readable as our other instances of significant whitespace.
I agree that binding the property after the dot notation to the first function in the command line (or the one after the previous dot notation) allows you to write code that is hard to read. But there's no programming language that can stop people from writing bad code. So i guess my question is this: is there ever a time when someone would write foo bar .baz()
currently in CoffeeScript? Sure, we chain the commands with empty space between, but in that case we always put the next command on the next line. And even when you take the one real world case that would brake with the new notation...
another_func $ 'a'
.button()
.click(load_ajax)
...I actually find it much more logical that .button() would bind to another_func:tion, since this allows us to process the whole function line at once, and only after that move to the next lines. Currently it's slightly backwards since you first have to process $('a') in your head, then .button(), then .click(...) and only then jumping all the way back to another_func.
Yep, CoffeeScript has intra-line significant whitespace. But maybe it's a good idea not to increase the quantity of it, especially by adding an easily confusable and barely debuggable significance of whitespace affecting the order of operations.
Actually, while I am not too keen on a b .c()
becoming a(b).c()
, I do like the cut of that multi-line jib.
$ 'a.popup'
.button()
.click -> popup this.href
Is it reasonable for a newline to close the outermost implicit parentheses in this case?
This came up on Stack Overflow recently. It would mean that you could write
js = require 'coffee-script' .compile coffee
which is pretty nice. And people have long been begging for
$ '#sprite'
.html str
.css left: x, top: y
.bind 'click', clickHandler
to be supported somehow. The main counterargument Jeremy and others have expressed toward such proposals is that they overload indentation with multiple meanings. This proposal, on the other hand, makes the indentation non-significant, the same way that
a +
b
is a perfectly acceptable way of writing a + b
. I find that to be consistent and beautiful.
Let's remember that "it allows ugly code" isn't a compelling argument against a language feature—ugly code will always be possible. The question is, rather, "Does it encourage ugly code?" And this proposal, in my view, does the opposite, allowing more stylistically consistent code to be written. An enthusiastic +1 from me.
I would really, really like this to happen. There isn't a great reason to write foo bar .baz
if you mean foo bar.baz
, but there are a lot of reasons to write jQuery- or connect-style method chains. CoffeeScript already promotes aesthetic structure as defining behavior; this just makes the structure more beautiful and more expressive.
I wanted to propose why this issue is more in line with CoffeeScript's philsophy by preserving significant whitespace. Right now, CoffeeScript is less ambiguous in one way because it converts
brush
.startPath 'red'
.moveTo 10, 10
into
brush.startPath 'red'.moveTo 10, 10
where the .
operator responds to the trailing argument in the previous line. Yet when reading this code, it is ambiguous to which object we are referring. CoffeeScript will currently let you can nest method calls for different objects in the same indentation level (brush
vs the string 'red'
), which seems unintuitive. Indentation, or nesting level, should really imply statements are in the context of the most recent line of a lesser indentation level. Reading it in this way, each method call refers to brush
.
Depending on how #1495 is resolved, this definition may not extend to nested chaining syntaxes (though parentheses could still be omitted); but one possibility to get the current behavior is to make greater indentation override the implicit parentheses:
brush
.startPath 'red'
.moveTo 10, 10
Compile to brush.startPath 'red'.moveTo 10, 10
as before.
OP @jussiry, +1
I just wanted to add the main Benefit of supporting this feature, is to not have to go backwards into your code, to have to add parenthesis to do something simple.
js:
x('hi')
~> x('hi').y
coffee:
x 'hi'
~> x 'hi' .y
doesn't work >:[
... have to go backwards and add parenthesis x('hi').y
wth, why can't I write it this way x 'hi' .y
!?!
if I meant x('hi'.y)
I would've written in coffee x 'hi'.y
!!! (no-space after 'hi')
This happens a lot! (having to go backwards just to add parenthesis ... especially in client-side jQuery code and other libraries that does chaining... it's really annoying to have to go backwards...
And I think yes, white-space is important, actually I think any character in programming is important... every ;
every "
every _
... I believe we as efficient programmers, likes code, with as minimum amount of characters as possible, that's still readable/maintainable.
We account for every character, trying to get rid of as much as possible.
Coffee-Script was made I believe, to make coding JavaScript more joyable and efficent, one of the main benefits is a cleaner syntax, with less ; ( ) { } ...
I think Coffee-Script programmers, translates _
white-space_
after function/method, in their minds to mean (
parenthesis)
...
x y => x(y)
x.y z => x.y(z)
x y .z => x(y).z
@quangv If you hate all the punctuation and stuff and prefer whitespace, you might be interested in this other language. ;D
@erisdiscord why does CoffeeScript translate x 'hi'
into x('hi')
?
Why not just keep the parenthesis?
@quangv I'm going to guess that CoffeeScript's allowance for omitting parentheses, as well as its precedence rules in terms of missing parentheses, come directly from Ruby. Which I think is sensible. (It also translates x y .z
as x(y.z)
, by the way)
I do not think people translate spaces to mean parentheses. I think they read it to mean:
/(\W+)\w+(\W+)/ => $1 + "(" + $2 + ")"
which is pretty easy to remember if you think about it. So the following expressions are pretty straightforward this way:
a b => a(b)
a b c => a(b(c))
a.b c => a.b(c)
a b.c => a(b.c)
a b.c d e.f => a(b.c(d(e.f)))
That said, x y .z
admittedly has an ambiguous interpretation, but only if you think about it different ways. If you hold to the rule above, then you could think about it this way:
x y => x(y)
x y .z => x(y(.z)) # uh oh, invalid! try removing the level of parens we just added
=> x(y.z) # ok!
Or, something like this, which is probably closer to how CoffeeScript actually parses x y .z
:
"x" -> ["x"] # yay identifier
" " -> ["x", ??] # hmm, maybe this is a function? i'll wait
"y" -> ["x", "y"] # this must be a function arg. push onto stack
" " -> ["x", "y", ??] # another upcoming arg?
".z" -> ["x", "y.z"] # huh! this starts with a dot. welp, it can't be an identifier, identifiers can't start with a dot. i'll just append to the prev token
["x", "y.z"] => "x(y.z)" # code translation
Translating whitespace as a placeholder for parentheses is fine for the cases that work:
x y => x(y)
x y.z => x(y.z)
x.y z => x.y(z)
x y .z => x(y).z
but doesn't work for everything:
x y z => x(y)z ??
a b.c d e.f => a(b.c)(d)(e.f) ??? oh good grief
@mcmire, addmittedly this could add some complexity to the current way of parsing code, but the idea would be to use [whitespace].foo
as special case that would causes the expression before that to be evaluated, before continuing with the parsing.
This would cause one weird situation though: how to parse if there is white space on both sides of the dot? I'd just throw a parse error in this case, since i can't see any reason for using such a syntax.
(It also translates x y .z as x(y.z), by the way)
lol, yeh I threw that in there because x y .z
seems like it should translate to x(y).z
Also I just meant translate as a mental image of the code, not a literal translation of _
space to mean (
)
...It's easy enough to know that [space]name
to mean (name
perhaps it's better to look at [space]
to mean just the opening (
parenthesis, and it just closes on it's own... and [space].
force closes the most previous (
, special case like @jussiry said.
I like your parenthesis examples.
x y .z
=> x(y(.z))
invalid! but at least i "makes sense" in mind?
but still if I wanted to write x(y.z)
in coffeescript I'd write it x y.z
, I wouldn't include unnecessary space
x y z => x(y)z ??
nope it'll be in my mind x(y(z
and coffee compiles to => x(y(z))
a b.c d e.f => a(b.c)(d)(e.f) ??? oh good grief
This would be a(b.c(d(e.f
in my mind and compiles to => a(b.z(d(e.f)))
Just wanted to point out
It's currently _impossible_ to write x(y).z
in CoffeeScript without (
parentheses)
. This is an injustice for me as a CoffeeScript parentheses-function-call-hating programmer ... there should be a way.
x y .z # nope
x y
.z # nope
... that's the thing that gets me... I <3 CoffeeScript, it has trained me, to _not use_ parentheses for function/method calls...
x 'hi'
x.y 'hi'
but the only time I have to use parentheses again is for chaining! and then I have to go back and _add that (
...
x(y).z # ahh what the freak, I thought I was rid of those ( ) !!! >:[
It's so freaking weird while programming to write
x y
and then wantin x(y).z
# the only way to write this syntax in CS is to write it the exact same way in JS
It feels unnatural as a CoffeeScript programmer to have to add (
and )
just to chain .z
onto x y
... especially when chaining is so common on client-side coffeescript/javascript programming ... you have to go backwards a lot... very annoying to me.
the only way to write this syntax in CS is to write it the exact same way in JS
Not exactly. I would have written your example as (x y).z
. I find that much more natural because x y
produces a value, upon which we are using the member access operator .
.
@michaelficarra
(x y).z
ah good point... you still have to use (
)
though... and when the code started as x y
, it's really annoying to have to add those, and change code to (x y).z
@quangv: Yes, I agree. That's a very common bother for me. This syntax seems like a pretty good solution, although not the most readable. But I'd probably make use of it.
This is an injustice for me as a CoffeeScript parentheses-function-call-hating programmer
See, this I think is a mistake that many people who've jumped on the CoffeeScript bandwagon have made. A thing I learned a while back is just because CoffeeScript lets you do something, doesn't mean you should do it everywhere. Just as you have the option to remove parentheses, you also have the option to leave them in where it would be more readable. Same goes for one-line if statements, comprehensions, etc. CoffeeScript gives you a lot of tools and it's quite easy to abuse them.
I'm not disagreeing with you that it's annoying that you have to add back parens in the x(y).z
case. I just don't think the solution is to allow leaving out the parens, I think the solution is to get into the habit of using parens for require statements only. This is what I do, it's only two extra characters (!), and it's future-proof.
The only case I see where x y .z
makes sense to be interpreted as x(y).z
is when written
x y
.z
(it's still kinda weird but line breaks actually mean something in CoffeeScript so it seems okay). In that light, perhaps x y ; .z
is the solution here. So you could write require "foo"
and then come back later and write require "foo" ; .bar
(or require "foo";.bar
if you wanted to save two characters) and boom, problem solved.
@mcmire Exactly. It's like a Perl programmer writing all of their conditionals like this because Perl allows it:
do { stuff();
moreStuff() } if (condition);
@quangv I was kidding! But really, CoffeeScript isn't about getting rid of parentheses. The parenthesisless syntax for function calls is prettier and possibly more readable on its own and it allows us to make nicer-looking DSLs, but stuff like foo 37 .bar()
is harder (for me) to head-parse than foo(37).bar()
.
@mcmire
I think the solution is to get into the habit of using parens for require statements only.
Oh I use chaining in way more things then require
statements... as I learned from #1495 (the multi-line discussion of chaining... I consider this thread the single-line discussion of chaining) for require to use CoffeeScript's destructuring assignment
{bar} = require 'foo'
This is an injustice for me as a CoffeeScript parentheses-function-call-hating programmer
I'm just saying that while writing CoffeeScript codes, I've been train and got into the habit of _not_ using parentheses for function/method calls... and it's a pain to have to go back into my existing code, add parentheses, just to make chaining work.
In other languages/communities, the general conventions seem to tend towards:
- Parentheses for functions where you are interested in the return value.
- Parentheses optional for functions where you are interested in capturing the return value in a local variable for later use, or capturing the return value as the final/return value of the current scope/expression.
- Parentheses required for functions where you are chaining/building off the return value immediately.
- No parentheses for functions where you are calling them as statements and ignoring the return value.
Even though the language lets you skip parentheses at times, that's not the go-ahead to skip parentheses all the time.
@yfeldblum, what do you think the CoffeeScript way should be?
CoffeeScript is inspired by Python
In Python, there's the credo
"There should be one-- and preferably only one --obvious way to do it."
Now does the below seem to go with or against this principal ?
x y .z # => x(y.z)
x y.z # => x(y.z)
I think the above is an oversight, unless you can find somewhere or someone who thinks the above is a feature, to let you do, dot notation with space in-between...
The status-quo isn't good enough sometimes, if CoffeeScript wanted to be JavaScript, it wouldn't exist.
@erisdiscord
but stuff likefoo 37 .bar()
is harder (for me) to head-parse thanfoo(37).bar()
.
Yes it is, but it's easier to program.
The act of actually adding .bar
onto foo 37
, is much, much simpler, and less annoying, if foo 37 .bar()
was supported instead having to, use your mouse, or keyboard, changing that [space]
after foo
to a (
, then going back to where you was, end of the line, adding ).bar()
...
ahh, annoying just to think about it... it totally _kills the flow_ of you programming.
Looking at other people's code, even looking at your old code, it takes a bit to grok it, but while programming, if you type foo 37
... then you add .bar()
... you have 100% clarity as to what it is you want and expect...
But yeah I don't think CoffeeScript's goal is to have the must beautiful, understandable code in the world (maybe it might be...) but I think it's bigger goal is having one of the best programming experience in the world...
If we wanted 100% understandable, no margin for confusion code, we'll use parenthesis all the time.
foo(37)
is much more self-explanatory then foo 37
... so I ask again, why does CoffeeScript support omissions of parentheses on function/method calls?
maybe CoffeeScript needs a Philosophy ? ...
@quangv CoffeeScript's Philosophy with parentheses isn't that hard to interpret. You can put parentheses in lots of places, even in places where it's only to help the reader. But, unlike Python, CS doesn't require you to put them in places where there's a natural order of operations. Python doesn't require you to write "(5 * 3) + 2" in order to get 17; you are allowed to write "5 * 3 + 2". OTOH Python won't let you write "sum x, y", even though that many folks would say that code has one pretty obvious interpretation. CS just gives you a little more flexibility. It's really not that big a deal.
The whole argument about having to backtrack seems kind of specious to me. If I say x + y, I don't have to surround it in parentheses. But then if I want to double the expression, I have to backtrack and write "(x + y) * 2". That's true even in Python.
maybe CoffeeScript needs a Philosophy ? ...
We got some:
- What Ruby does can't be bad.
- The more your code reads like English, the better.
- What Ruby does can't be bad.
- The more your code reads like English, the better.
Nice... where you get those?
@showell ... interesting interpretation, very laissez-faire... parentheses are optional, only when they are not... my only comment is (x + y) * 2
has a very known order of operations... what's the order operation of x y .z
...
I still don't like how that [space]
in between y & .z means nothing...
It's like the [space]
in between x & y, means something, why not the later?
What do you guys think about this, for single-line & multi-line chaining support.
x y .z
# => x(y).z
x y
.z
# => x(y).z
CoffeeScript is inspired by Python
Well, CoffeeScript is inspired by a few languages. Comprehensions, significant whitespace, and leaving off "end's" come from Python, the rest comes from Ruby/Perl and some of the Harmony/ES6 proposals.
maybe CoffeeScript needs a Philosophy ? ...
I think it already does, really: be a better version of JavaScript. CoffeeScript starts with JavaScript and makes it suck less by adding 'modern' features that have already been well accepted in other languages.
In any event, your example (against Python's motto) doesn't really fly because x y .z
isn't an intended alternate way to write x y.z
, it's an edge case, something that the parser is forced to handle because it isn't a feature of the language. Actually the whole parentheses thing is a red herring -- the edge case is x .y
. There's two ways to handle this -- either convert it to x.y
, or fail with a parser error -- and obviously we know what CoffeeScript does. I mentioned that its behavior comes from Ruby, but I just checked and Python not only does this, but more importantly, JavaScript does as well. (Where JavaScript got it from, I'm not sure, perhaps C?) So while it's kind of silly -- honestly I would expect a parse error -- that's what happens. It's a historical thing. (Although, not entirely illogical; after all, you can say foo[bar]
just as you can say foo [bar]
... so in that light foo .bar
is just being consistent. You can also say foo \n .bar
or foo ; .bar
, so foo .bar
is also being consistent there too.)
Normally this behavior would be a non-issue and could be "fixed", because being an edge case and not a feature, no one would (should) actually write x .y
in their code on purpose, and that's as complicated as an expression like that can get without creating a parse error (because x y .z
is not valid JavaScript), so handling it in a weird way is inconsequential, so no one would (probably) mind if you changed it.
It's only consequential now because CoffeeScript has the ability to fill in missing parentheses.
my only comment is (x + y) * 2 has a very known order of operations... what's the order operation of x y .z ...
Exactly -- which takes precedence, placement of parenthesis or autocorrection of method calls. So going back to my take on the "philosophy" of CoffeeScript -- I think when ambiguity arises like this, the correct behavior would be to do what JavaScript does first, then layer CoffeeScript-specific behavior on top of that. That's why I think x y .z
should remain as x(y.z)
-- because with parenthesis, JavaScript would parse x(y .z)
as x(y.z)
, so parsing x y .z
as x(y.z)
is just the next logical step. In other words, apply the JavaScript rule first, then CoffeeScript. I think this is true to the design of CoffeeScript itself, if you look at other things in CoffeeScript.
You asked why does CoffeeScript let you omit parentheses and I say, for the same reason that it drops parentheses around if statements and lets you write foo if bar
or foo for foo in bar
. These features serve their purpose when you use them if 1) you write less code and 2) your code is more readable in the process. If you write less code but your code is less readable, then it is probably not wise to use them. (Yes it is true that it is easier to write, but then again, all code is easier to write than read. Always.) In the case of require "foo" .bar
it is less readable only because we have been trained to expect (through previous languages that use dot notation for OO code, and just, I think, the natural way that people read code, which is left-to-right) that a dot is connected to whatever comes immediately before it, and that whitespace (well, spaces) in this scenario are insignificant and should be ignored.
Nice... where you get those?
Jeremy's mind.
Thanks for thorough reply. A lot of the reasons you make against proposed [white-space][dot]
syntax could be had in support of the syntax.
First let me clear up:
"For x .y
, I think we can look at it like (x).y
and compile it to x.y
=p"
#1495
I totally think x .y should still be x.y ... weird I know, hah but it makes the most sense to me. if we wanted to chain x, write it as x().y
... there's no annoying back-track-coding that takes place.
x y .z
isn't an intended alternate way to writex y.z
, it's an edge case, something that the parser is forced to handle because it isn't a feature of the language.
Totally agree with you. It's an 'oversight', 'toleration', not really a 'feature'.
Thanks for checking how Ruby, Python handles this. I kinda agree that throwing an error might be worse than just pretending that the space isn't there... but that's up for debate as well. (like JS supporting lines without ending in ;
) ... whoa foo [bar]
is worst then foo .bar
!
Normally this behavior would be a non-issue and could be "fixed", because being an edge case and not a feature, no one would (should) actually write
x .y
in their code on purpose, and that's as complicated as an expression like that can get without creating a parse error (becausex y .z
is not valid JavaScript), so handling it in a weird way is inconsequential, so no one would (probably) mind if you changed it.
Yes, well put! Hopefully not... x .y
i think should still be x.y
, but x y .z
compiling into x(y).z
would make a lot of us salivating for a chaining syntax happy. :)
I think when ambiguity arises like this, the correct behavior would be to do what JavaScript does first, then layer CoffeeScript-specific behavior on top of that. ... I think this is true to the design of CoffeeScript itself, if you look at other things in CoffeeScript.
Yes, I agree, default to JS when there's no CoffeeScript-specific behavior. Hopefully we can have a CoffeeScript-specific chaining syntax for everyone who wants it, but inconsequential enough that people who don't use this new CS-specific syntax, won't be impacted by it, and all their current CS code, compiles and works the same... tall order, but hopefully we can get-her-done... back-track-coding is just way too annoying.
You asked why does CoffeeScript let you omit parentheses and I say, for the same reason that it drops parentheses around if statements and lets you write foo if bar or foo for foo in bar. These features serve their purpose when you use them if
- you write less code and
- your code is more readable in the process.
...
In the case of require "foo" .bar it is less readable only because we have been trained to expect that a dot is connected to whatever comes immediately before it, and that whitespace (well, spaces) in this scenario are insignificant and should be ignored.
:)
I think this x y .z => x(y).z
agrees with your 1., less code, and 2. readability is in the eye of the beholder, and I also agree with you that once we are trained of this new behavior, it'll be that much easier to understand the code.
Some non-Coffee-er cold argue foo(bar)
is easier to read than foo bar
... but I don't think any Coffee-er is willing to give up this great syntax... CoffeeScript's makes white-space significant and it's clever use of white-space is one of the reasons I think that makes it so awesome.
@satyr I think it should be written somewhere... I believe more lexical conflicts of syntax, taste, and grammar would come up in the future that maybe a simple "doesn't comply with our Philosophies" would be a great response... and people can argue the merit of a particular principal before a proposed syntax enhancement or change. The FAQ is a great lift-off point and the official docs has philosophies and principals littered through-out. :) Keeping CS awesome I think is everyone's main goal...
@quangv So this is interesting, look what coco does: http://satyr.github.com/cup/#c:a%20b%20.c%20.d%20e /cc @satyr
@quangv So this is interesting, look what coco does: http://satyr.github.com/cup/#c:a%20b%20.c%20.d%20e /cc @satyr
Oh wow, that's cool! Nice job @satyr ... I didn't realize what Coco was...
http://satyr.github.com/cup/#c:x%20y%20.z awesome, perfect... coffeescript should compile this way too.
Might be worth a read:
I'm pretty new to using CS, but wished for quangv's syntax almost immediately. I've been writing a great deal of jQuery and Jasmine specs. Just yesterday I found myself writing something like this 50+ times:
expect(condition).toBe true
or
`````` expect($('#id').attr('foo')).toBeUndefined()```
But that's just JS. What I wanted to write was:
expect condition .toBe true
and
expect $ '#id' .attr 'foo' .toBeUndefined()
CS is a distinct language of it's own, so arguments concerning what JS programmers expect shouldn't be relevant. Inline whitespace already has semantics (removing parens ;) Is there a reason parenthesis couldn't be restricted to enforcing precedence and allow a single space to replace the use of parenthesis for function calls? I don't see why you couldn't even write this:
expect $ '#id' .attr 'foo' .toBeUndefined
It's not like I can have BOTH a property on an object AND a method that both have the same name.
As for this syntax encouraging the writing of bad code, more helpful compiler errors and a CoffeeScript version of lint would certainly help with that. As others have already said, you can't stop people from writing bad code. If they want to write clever code, it's going to be difficult to read and debug.
I agree with quangv wholeheartedly. Going back to add those parens is a pain in a language that (in my limited experience) tries to eliminate as much pain as practical.
Has anyone seen a pull request for this one?
expect $ '#id' .attr 'foo' .toBeUndefined()
@gijohnson-usgs, what is this supposed to do? It could be expect($('#id')).attr('foo').toBeUndefined()
, or expect($('#id').attr('foo')).toBeUndefined()
, or maybe expect($('#id').attr('foo').toBeUndefined())
...
Quite ambiguous :(
If this was added, the compiler should just read it left to right and assume no nesting unless parens indicate it.
a b .c .d e, f .g()
should compile to
a(b).c.d(e, f).g();
For the jasmine example, parens are definitely needed, there's no way around it
expect($ '#id' .attr 'foo').toBeUndefined()
I think maybe for nesting, parentheses should still be used, so the above should be written
expect($ '#id' .attr 'foo').toBeUndefined()
# should eql
expect($('#id').attr('foo')).toBeUndefined()
and
expect $ '#id' .attr 'foo' .toBeUndefined()
# should eql
expect($('#id').attr('foo').toBeUndefined())
@renekooi, you're right. That IS ambiguous. I've been thinking about this and the problem is nested function calls:
a b .c
~> a(b).c
works fine
a b c .d ~> a(b(c).d) or a(b(c)).d
? doesn't work
I've been thinking about this for a couple of days and I think I came up with a solution (don't know if anyone else would like it). If " .a" closes a function call before accessing the a property or invoking a function, then you could have:
a b c .d
~> a(b(c).d)
a b c .d .e
~> a(b(c).d).e
taking this a step further and thinking of " ." as generically closing the the previous function call you have:
a b c . .d
~> a(b(c)).d
a b c.d e . . .f
~> a(b(c.d(e)))
now that turns into quite a bit of whitespace, and I'm not sure how often people would use this, but shorthand could be:
a b c d ...e
~> a(b(c(d))).e
a b c .d ..e
~> a(b(c).d).e
each additional "." denotes closing another paren. If it's common to desire closing all of the nested function calls, you could use "!.":
a b c d e f !.g
~> a(b(c(d(e(f))))).g
The "!" is an exclamation in english and can intuitively thought of as "close ALL of those parens!".
In that case, I could write my original example as:
expect $ '#id' .attr 'foo' . .toBeUndefined()
,
expect $ '#id' .attr 'foo' ..toBeUndefined()
or
expect $ '#id' .attr 'foo' !.toBeUndefined()
Let me know if I overlooked anything again.
@gijohnson-usgs, interesting thinking, but starts to get pretty complex. I'v stated this before, but I think the cleanest way would be to just interpret 'space dot method' the same way you are suggesting exclamation mark to be used. This would allow us to do clean chaining, e.g.
$ '#some_el' .find '.child'
.css 'color', 'white'
.click ->
$ @ .remove()
Anything more complex than that, and you'll most likely want to use parens for easy reading anyway.
@jussiry, it does start to get complex. And nesting that many function calls would likely make for quite unreadable code anyway (there are reasons we use variables). The only way to know for sure is to try it out.
Don't know when I'll have time to go through my code to find examples of deeply nested function calls. If I found them, it'd prbly be a good time to refactor anyway. However, the jQuery I'm writing at the moment could definitely benefit from you're example.
jussiry commented
@gijohnson-usgs, interesting thinking, but starts to get pretty complex. I'v stated this before, but I think the cleanest way would be to just interpret 'space dot method' the same way you are suggesting exclamation mark to be used. This would allow us to do clean chaining, e.g.$ '#some_el' .find '.child' .css 'color', 'white' .click -> $ @ .remove()
Anything more complex than that, and you'll most likely want to use parens for easy reading anyway.
agreed +1
This is all pretty interesting, but looking at the one sensible chaining case, is @quangv's sample really any worse just written out as:
$('#some_el').find('.child')
.css( 'color', 'white' )
.click ->
$(@).remove()
Personally, I find that a lot easier to parse visually, which is the whole point, right? Human friendly code?
Some people reason code faster with parenthesis and some without, depending on what kind code they are used to reading. In this example, i would definitely write find
and css
methods without parenthesis - jQuery selectors i'm not so sure about. Assuming there is no negative sides effects from this addition that anyone can think of, i'm definitely in favor of it.
Unlike other superfluous punctuation (yes, I'm talking about you, semicolon), parentheses have a high signal-to-noise ratio. They connote perfectly and fairly succinctly the developer's intent.
Paren-free syntax is a fad. I'm against this proposal (for hopefully obvious reasons). Parentheses generally help readability. There are certainly cases where you can obviously infer them like in this parenthetical statement, but I digress but it is silly to bend over backward to avoid them.
@showell I agree that doing all you can to avoid parenthesis is a very bad idea. But then again there are cases where leave them away helps to keep code cleaner without making it harder to reason with (e.g. chaining with row changes). So yes, with this addition you could write code that is ugly and hard to reason with, but unfortunately that's possible in every programming language that exists anyway, so i don't see that as good argument against this addition.
@jussiry I'm not sure I understand your "that's possible in every programming language that exists" comment. Some languages enforce explicit parentheses more than others. Python is more strict about parentheses than Ruby, for example. The tradeoffs are obvious enough. Python code is easier to reason about than Ruby (no need to guess about where the parentheses would be), but it's also more visually cluttered (due to the parentheses).
All I can tell you is that Python is more pleasant to read than Ruby. It's easy to ignore parentheses when they're just visual clutter; it's much harder to infer grouping when parentheses are omitted.
I'm not advocating for CS to completely abandon paren-free syntax. It makes a fairly nice tradeoff now. I'm just saying enough is enough.
@showell Fair point. I guess I'm leaning more into the camp that wants to give programmers the right to choose how they want to write their code, but if I were in a position where I'd have to read lots of code written by less-than-expert programmers I'd probably also prefer more stricter rules.
@jussiry There's definitely a tension between strictness/predictability and artistic freedom. In the early stages of developing a programming language, I would err on the side of strictness. It's always easier to loosen up a language than tighten it down (because of backward compatibility concerns).
I would argue that CoffeeScript, while promising and beautiful, is still in the very early stages of development. For godsake, it doesn't even have line mapping support.
@showell commented
Unlike other superfluous punctuation (yes, I'm talking about you, semicolon), parentheses have a high signal-to-noise ratio. They connote perfectly and fairly succinctly the developer's intent.
Good points. I guess it depends on what the Philosophy of CoffeeScript,
[CoffeeScript]... expose the good parts of JavaScript in a simple way
I'd assume readability is one of the philosophy of CoffeeScript as well... @showell, good points about Python vs Ruby... I'd go with Python 110% !!!
I use to be one of the proponents of no parentheses... but if it goes against the Philosophy of CoffeeScript, then I am against it.
@showell when you say line mapping support you mean the debugging error messages?
@quangv I think the Philosophy of CoffeeScript can have many interpretations, so I wouldn't go so far as to say that no-parens goes against it. It's really just my own opinion.
By line-mapping support, I specifically mean that the compiler doesn't emit line number mappings, but, yes, one of the consequences of that omission is that debugging error messages are JS only.
I'm way late to this party, but just spent a long time reading through this and a few of the other big related threads. Very interesting points on all sides.
Mostly though, it's so great to see civil and respectful discussion. Thanks guys -- what a great community.
I just wanted to toss in a +1 to support chaining on newlines -- so useful for jQuery.
That's what it boils down to for me -- it seems this is possible to support and worth it given that jQuery is such a popular use case. Pragmatic and effective.
just wanted to add... disdain of parenthesis could be reduced by using the do
command:
do foo # foo()
@quangv Sure, but that just adds a trailing ()
to whatever you put after the do
, which is probably not what you want.
@mcmire I find myself writing do action
as opposed to action()
, just my disdain of parentheses... (thanks to CS)
Another problem of this is when you are using promises.
For example:
doAsyncThingWithArguments argument1
.then (data) ->
handleData data
will compile to
doAsyncThingWithArguments(argument1.then(function(data) {
return handleData(data);
}));
instead of
doAsyncThingWithArguments(argument1).then(function(data) {
return handleData(data);
});
which has led me to a lot of unintended behaviour and a lot of extra parentheses.
Note the leading dot syntax on a new line is on master now (via #3263).
To the discussion here I will only add that the semantics we use are:
a b c
.d
.e
compiles to a(b(c)).d.e
, that is opposite (and hopefully much simpler) than what @gijohnson-usgs proposed (for inline syntax) above.
I think we got what we wanted :-).