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

Unnecessary parentheses in function calls. #1407

Closed
jussiry opened this issue Jun 1, 2011 · 75 comments
Closed

Unnecessary parentheses in function calls. #1407

jussiry opened this issue Jun 1, 2011 · 75 comments
Labels

Comments

@jussiry
Copy link

jussiry commented Jun 1, 2011

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).

@erisdev
Copy link

erisdev commented Jun 1, 2011

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)

@michaelficarra
Copy link
Collaborator

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.

@jussiry
Copy link
Author

jussiry commented Jun 1, 2011

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).

@jussiry
Copy link
Author

jussiry commented Jun 1, 2011

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.

@satyr
Copy link
Collaborator

satyr commented Jun 1, 2011

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.

@thejh
Copy link
Contributor

thejh commented Jun 2, 2011

👍
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 )

@erisdev
Copy link

erisdev commented Jun 2, 2011

@thejh that's horrifying.

@thejh
Copy link
Contributor

thejh commented Jun 2, 2011

@erisdiscord I'd rather call it "consistent".

@thejh
Copy link
Contributor

thejh commented Jun 2, 2011

Humm... on a related note, what would you think about doing operator precedence using spaces? Something like

1+1 * 4 # 8, not 5

@erisdev
Copy link

erisdev commented Jun 2, 2011

@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

@jussiry
Copy link
Author

jussiry commented Jun 2, 2011

@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()``.

@jussiry
Copy link
Author

jussiry commented Jun 2, 2011

@thejh, on the operator precedence: i like it. :) Haven't given it that much thought, though.

@jussiry
Copy link
Author

jussiry commented Jun 2, 2011

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.

@ricardobeat
Copy link
Contributor

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.

@ricardobeat
Copy link
Contributor

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 ', '

@satyr
Copy link
Collaborator

satyr commented Jun 4, 2011

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.

@ricardobeat
Copy link
Contributor

@satyr but that kind of defeats the purpose of not having to nest things in parenthesis.

@yfeldblum
Copy link

"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.

@satyr
Copy link
Collaborator

satyr commented Jun 5, 2011

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));

@michaelficarra
Copy link
Collaborator

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.

@jussiry
Copy link
Author

jussiry commented Jun 6, 2011

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.

@yfeldblum
Copy link

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.

@erisdev
Copy link

erisdev commented Jun 6, 2011

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?

@TrevorBurnham
Copy link
Collaborator

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.

@reissbaker
Copy link

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.

@tcr
Copy link

tcr commented Oct 31, 2011

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.

@quangv
Copy link

quangv commented Dec 5, 2011

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

@gijohnson-usgs
Copy link

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?

@goto-bus-stop
Copy link

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 :(

@ajoslin
Copy link

ajoslin commented Mar 24, 2012

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()

@quangv
Copy link

quangv commented Mar 25, 2012

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())

@gijohnson-usgs
Copy link

@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.

@jussiry
Copy link
Author

jussiry commented Mar 26, 2012

@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.

@gijohnson-usgs
Copy link

@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.

@quangv
Copy link

quangv commented Mar 27, 2012

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

@csubagio
Copy link

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?

@jussiry
Copy link
Author

jussiry commented Mar 30, 2012

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.

@showell
Copy link

showell commented Mar 30, 2012

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.

@jussiry
Copy link
Author

jussiry commented Mar 30, 2012

@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.

@showell
Copy link

showell commented Mar 30, 2012

@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.

@jussiry
Copy link
Author

jussiry commented Mar 30, 2012

@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.

@showell
Copy link

showell commented Mar 30, 2012

@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.

@quangv
Copy link

quangv commented Mar 30, 2012

@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?

@showell
Copy link

showell commented Mar 31, 2012

@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.

@aseemk
Copy link
Contributor

aseemk commented May 13, 2012

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.

@quangv
Copy link

quangv commented Jun 8, 2012

just wanted to add... disdain of parenthesis could be reduced by using the do command:

do foo  # foo()

@mcmire
Copy link

mcmire commented Jun 8, 2012

@quangv Sure, but that just adds a trailing () to whatever you put after the do, which is probably not what you want.

@quangv
Copy link

quangv commented Aug 31, 2012

@mcmire I find myself writing do action as opposed to action(), just my disdain of parentheses... (thanks to CS)

@OscarF
Copy link

OscarF commented Sep 16, 2013

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.

@vendethiel
Copy link
Collaborator

@OscarF see #1495

@xixixao
Copy link
Contributor

xixixao commented Dec 2, 2013

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.

@vendethiel
Copy link
Collaborator

I think we got what we wanted :-).

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

No branches or pull requests