jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.5k stars 1.99k forks source link

`do ->` as `(->)()` #788

Closed satyr closed 13 years ago

satyr commented 14 years ago

We all know the usefulness of this idiom, but the ugliness of the parens is intolerable. new -> works sometimes, but is flawed.

Would be really nice if we could find a way to pass arguments as well.

for l in elements then do (lmn = l) ->
  setTimeout -> lmn.click()
satyr commented 13 years ago

s/ consensus / Stan's view /

We should support it--"How do you create a function and call it immediately?" is one of the most frequent questions, and I hate to say "You have to use parens."

zmthy commented 13 years ago

Alright, it's the same deal with function binding. We have an example of a custom Function::bind in the FAQ, we can do the same with scoping.

karl commented 13 years ago

While the using parens is a minor annoyance, most of the proposals discussed here seem even more convoluted, especially those doing odd wrangling of variables.

So a -1 from me at the moment.

TrevorBurnham commented 13 years ago

I thought we had a near-consensus around adding do, and just weren't 100% there on the argument-passing syntax. Here's a concrete proposal (paired lines are equivalent):

do func         # for consistency's sake
func()

do -> f()
(-> f())()

do (x = y) -> x()
((x) -> x())(y)

do (x) -> x()   # shorthand for `do (x = x) -> x()`
((x) -> x())(x)

And of course do => would work the same as do ->, except that the function is called in the current context.

I for one would find this to be a very useful feature. Even in the trivial examples given above, the do lines read much more cleanly than the old-style self-executing closures. Big +1.

satyr commented 13 years ago

do (x = y) -> x()

Doesn't make sense with current syntax. Maybe we should have default arguments, eventually.

TrevorBurnham commented 13 years ago

Yes, I was going to resurrect default arguments as a separate issue after this one was resolved... but since you find the syntax jarring (since (x = y) -> wouldn't compile without the do in front of it), I'll go ahead and raise it now.

[Update: It's issue 802.]

satyr commented 13 years ago

http://github.com/satyr/coffee-script/compare/master...do

satyr commented 13 years ago

do (x = y) -> x()

Now possible in combination of my defarg branch.

StanAngeloff commented 13 years ago

OK something that's been bothering me -- if we killed splices/slices using [..] as they can be replaced with .slice instead, why do we want a do feature that can be implemented as a one-liner? See the inconsistency?

satyr commented 13 years ago

if we killed splices/slices using [..] as they can be replaced with .slice instead, why do we want a do feature that can be implemented as a one-liner?

  • This issue is for an aesthetic improvement similar to the braceless object (all it does is to let you omit {}). "Calling bare functions without parenthesizing them" isn't possible without this proposal.
  • "Having to declare a function to achieve it" is a clear sign of a feature needed. Sp?lice was not.
  • Sp?lice was a sugar that did less than its nonsugared equivalent. do is not.
TrevorBurnham commented 13 years ago

I would add that there are significant performance gains to be had from using the mess of parentheses that do compiles to, rather than writing your own equivalent function:

http://jsperf.com/self-executing-closure-vs-run-function

Though, there is a tradeoff in code size: r(...) takes fewer bytes than function{...}(). Still, the fact that I've seen self-executing closures in production code many times, and never seen a "run closure" function in production code, is telling.

Besides, do (x = 1, y = 2) -> ... reads much, much more clearly than run (((x, y) -> ...), 1, 2), wouldn't you say?

StanAngeloff commented 13 years ago

< offtopic >

Testing JavaScript performance and having reliable results is a myth... I can never get accurate results with JSPerf. I am on Chrome 9 and my tests are against everyone's expected results. 48 / 42 in favour of Run. Go figure.

< /offtopic >

As the discussion is moving along I am starting to like do if it is kept simple.

asyncEval: (next) ->
  do next

Looks pretty.

tim-smart commented 13 years ago

I'm pretty sure I am -1 on this.

do next is far better written as next().

for cat in litter then do (cat) -> cat.age++

is better written / more per-formant as

incrAge = (cat) -> cat.age++
for cat in litter then incrAge(cat)

I'm not sure if the cases where do is useful are common enough for it to be considered.

satyr commented 13 years ago

is better written / more per-formant as

Thanks for the bench, Tim. That brings us back to the magical rescoping issue; why it was bad, what we really need and how we can do for it.

So how about this:

# when we see a directly called function without parameters under `for`
for cat in litter then (-> cat.age++)()

# perform this conversion for good
_fn = (cat) -> cat.age++
_fn cat for cat in litter

And of course, the calling part is better written as do -> cat.age++ ;)

TrevorBurnham commented 13 years ago

@Tim-Smart While the usefulness of self-executing closures is debatable (they're mainly about keeping programmers from doing stupid things by keeping their variables isolated), they're common enough that people complain pretty often about the ugliness of having to wrap them in parentheses in CoffeeScript.

Also, there was a lengthy issue before discussing implementing some alternative means of calling functions without arguments without using parentheses. There are cases where such a syntax could greatly increase readability (to non-Lispers). While do next isn't such a case, contrast these:

if (trim(getName()) is trim(ownerName())) and open then welcome()
if (trim(do getName) is trim(do ownerName)) and open then welcome()

I find the reduction in nested parentheses to be a breath of fresh air.

Svoloch commented 13 years ago

do very useful in loops. So i recommend apply next:

do -> continue

(function(){
    return;
})()

do->break

var __break={};
try{
    (function(){
        throw __break;
    })()
}catch(__E){
    if(__E===__break)break;
    else throw __E;
}

do->return val

var __return=function(__arg){this.value=__arg;};
try{
    (function(){
        throw new __return(val);
    })()
}catch(__E){
    if(__E instanceof __return)return __E.value;
    else throw __E;
}
jashkenas commented 13 years ago

Since discussion on this ticket has settled down, I'm closing it as a wontfix -- here's why:

naturalethic commented 13 years ago

Here's how it is more compact in my case. I like having the ability to close some variables without having to use the more verbose method, or naming a function I don't need named.

do (x, y) ->  becomes  ((x, y) -> ...)(x, y)

# this?
for report in array(doc)
  ((report) ->
    sql.select 'order', { account_id: account.id, source_id: report.id['#'] }, true, (order) ->
      if order?
        update_order report, order
      else
        sql.insert 'order', { account_id: account.id, source_id: report.id['#'] }, (order) ->
          update_order report, order
    )(report)

# or this
for report in array(doc)
  do (report) ->
    sql.select 'order', { account_id: account.id, source_id: report.id['#'] }, true, (order) ->
      if order?
        update_order report, order
      else
        sql.insert 'order', { account_id: account.id, source_id: report.id['#'] }, (order) ->
          update_order report, order
StanAngeloff commented 13 years ago

Ah, I was just bitten by this. Since we removed the magic from generated closures on master I had to go back and change my code in quite a few places. This is jut ugly:

fs.stat item = path.join(directory, file), ((item) -> (error, stat) ->
  throw error if error
  if stat.isDirectory()
    scan [item], block
  else
    process.nextTick -> block item
)(item)

I mostly agree with Jeremy, apart from:

do is never more efficient than creating a nicely-named local variable to hold the function.

I like my code to be linear. If I define functions before I use them this breaks my rule.

satyr commented 13 years ago

naturalethic I like having the ability to close some variables without having to use the more verbose method, or naming a function I don't need named.

StanAngeloff This is jut ugly: ...

I've implemented the hack proposed in my last post on my branch. See it in action here.

jashkenas commented 13 years ago

FYI -- the magic to generate closures inside of loops is back on master.

jashkenas commented 13 years ago

FYI, again -- the magic to generate closures is back off master, for once and for all, and I've added do.

TrevorBurnham commented 13 years ago

And do is back off master... see current discussion at issues 959 and 960.