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

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

Closed
satyr opened this issue Oct 22, 2010 · 73 comments
Closed

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

satyr opened this issue Oct 22, 2010 · 73 comments

Comments

@satyr
Copy link
Collaborator

satyr commented Oct 22, 2010

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()
@TrevorBurnham
Copy link
Collaborator

Isn't the meaning of the above example the same as

for l in elements
  setTimeout -> l.click()

? I do like the idea, though. This would kill a lot of parentheses, and even cut my line count. +1.

@satyr
Copy link
Collaborator Author

satyr commented Oct 22, 2010

for l in elements
  setTimeout -> l.click()

We no longer have the magical scoping within for.

@hen-x
Copy link

hen-x commented Oct 22, 2010

. +1. Since do is a reserved word anyway, let's make it earn its keep -- and with a nice, logical meaning!

On that note, should do object.method be meaningful? is do a full prefix equivalent to invocation parens, or does it only work with a literally-supplied block?

@satyr
Copy link
Collaborator Author

satyr commented Oct 22, 2010

Implementing it as a unary operator that simply represents ()() was the initial thought, but maybe we can make it an aliase of () that special cases -> (()()) and => (().call(this)).

x * (y + z)x * do y + z

fn (if foo
  bar
else
  baz
)

fn do if foo
  bar
else
  baz

Kinda similar to Haskell's $.

@TrevorBurnham
Copy link
Collaborator

Yes, do object.method as a synonym for object.method() would be an unnecessary violation of TOOWTDI. The compiler should throw a syntax error when do is followed by anything but -> or =>, where it's clearly going to be the preferred syntax.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

This should just rescope existing variables, and automatically ensure this is preserved. We don't really need the function glyph there.
do l then setTimeout (-> l.click()), 1000
...
(function(l) {
setTimeout(function() {
l.click();
}, 1000);
})(l);

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

Looking at that, it's no longer entirely clear what's going on if we just use the do keyword. The function glyph is probably a good idea simply because it ensures that the new scope is clear.
do (l) -> setTimeout (-> l.click()), 1000

@satyr
Copy link
Collaborator Author

satyr commented Oct 23, 2010

should just rescope existing variables

Probably. Then it becomes identical to the let proposal (#728), only with a different keyword.

@TrevorBurnham
Copy link
Collaborator

I think it's best for new scopes to always be indicated by -> in CoffeeScript.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

I've implemented this with the test:
one = 1
func = null
do (one) -> func = -> one
one = 2
ok func() is 1
Should we ensure that this is preserved where necessary and disallow do =>? Or, seeing as how we're using the glyph anyway, should it be manual?

@satyr
Copy link
Collaborator Author

satyr commented Oct 23, 2010

Maybe:

  • do (v) ->((v) ->)()
    • do -> this(-> this).call(this)
    • do -> arguments(-> arguments).apply(this, arguments)
  • do (v) =>((v) ->).call(this, v)

@hen-x
Copy link

hen-x commented Oct 23, 2010

Hard to imagine when (->)() would be preferable over (->).call(this), though. Seems like a source of errors with little gain.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

True, but I'm concerned about the ambiguity. In all other cases, a use of -> changes the context.

Perhaps a different glyph could be used here, instead of do? That way it's still clearly some kind of function so generates a new scope, but now is also clearly special and not expected to hold the same inherent properties of -> or =>.
(l) +> setTimeout (-> l.click()), 1000

@hen-x
Copy link

hen-x commented Oct 23, 2010

Normally, the -> glyph doesn't specify a context at all; that happens at invocation. To me, the preceding do statement is introducing the context, so it's not inconsistent to let the function be defined by ->. It's just like fn = (->); fn.call(this). We're not retaining a reference to the invoked function, so it's not being permanently bound to the enclosing context, just executed there.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

Fair enough. So we just prevent use of =>, then?

What about preserving arguments?

@hen-x
Copy link

hen-x commented Oct 23, 2010

I say we don't forward the arguments, since it's clear that do -> represents a new function scope, and it's not possible to fake out the special properties like arguments.callee anyway.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

What do you mean by 'fake out'? If we directly pass the arguments object all of the associated properties will be there.

@zmthy
Copy link
Collaborator

zmthy commented Oct 23, 2010

Do you mean that arguments.callee should refer to the new scope?

@hen-x
Copy link

hen-x commented Oct 23, 2010

Sorry for being unclear. What I meant by that, was that if we defined do -> x() to compile as (function(){ x() }).apply(this, arguments), then the version of arguments within the do statement would have a different callee property value than the one outside of it. As per your fix in #792 however, this is no longer an issue :)

@TrevorBurnham
Copy link
Collaborator

I believe that do -> should mean (->)() and do => should mean (=>)() (which can be implemented as (->).call(this)). It's pleasingly consistent and makes the meaning of do nicely transparent. If, as sethaurus suggested, .call(this) is almost always preferable to () after a closure, then do => will become the standard idiom.

@zmthy
Copy link
Collaborator

zmthy commented Oct 24, 2010

Done and done. Nothing fancy, and shadows existing variables as expected. For example:
do (a, b) =>
becomes
(function(a, b) {}).call(this, a, b);

@jashkenas
Copy link
Owner

We can leave this on master for the time being, but I'd like to see an explanation of how/why you'd use it added to the documentation.

Right now, looking at:

do (x) ->
  x + y

... doesn't make much immediate sense. Where does the x parameter come from? We know that it has to be a variable defined in local scope ... but it breaks the consistency of all other functions, where a parameter is always passed in. Having an explanatory example would help.

@StanAngeloff
Copy link
Contributor

Can we rename it to something like local or capture? do doesn't really hint at what's going on. Jeremy also makes a good point about breaking consistency.

@TrevorBurnham
Copy link
Collaborator

I think do is the perfect keyword, conveying the meaning "run this next function immediately." (It's a nice added bonus that it's already a reserved keyword.)

As to Jeremy's example, I do find it a little surprising that

do (x) ->
  x + y

compiles to

(function(x) {
  return x + y;
})();

I would think it would be more useful for do (x) -> to be shorthand for do (x = x) -> (which doesn't work yet)... and I see that do (x) => does work this way, so I'm guessing that's the intention and it's just half-implemented right now. Right?

Here's a common use case: It's considered a best practice in jQuery plugins to look for the global named jQuery and call it $ within the scope of the plugin (since there may be some other library that's using the global name $). So, this looks like

(($) ->
  # plugin code
)(jQuery)

Once the do syntax is fully implemented, this can be streamlined to the much clearer

do ($ = jQuery) ->
  # plugin code

@satyr
Copy link
Collaborator Author

satyr commented Oct 24, 2010

I've removed the magic from do (x) -> case, so no magic on there.
In do (x) =>, we can think that magic is caused by => which is already quite magical.

@stephank
Copy link

I haven't seen this mentioned, but how about using object literal syntax to map formal to active parameters? For example, a jQuery plugin might do:

do ($: jQuery) ->
  $.fn.wiggle = ->
    # etc.

@TrevorBurnham
Copy link
Collaborator

The do (x = y) argument-passing syntax struck me as a little off at first, too, but the object-literal syntax would be pretty incongruous. For one thing, it would be odd that

do ($: jQuery) ->

couldn't be replaced with

obj = {$: jQuery}
do (obj) ->

Here's one way to think about the do ($ = jQuery) syntax: It's functionally equivalent to

do ($) ->
  $ = jQuery
  # do things with $...

with the exception that $ is also added to arguments. So in most cases, it's just a slightly more succinct and organized way of doing initial assignments.

@satyr
Copy link
Collaborator Author

satyr commented Oct 24, 2010

do () ->
  $ = jQuery
do ($) ->
  $ = jQuery

@TrevorBurnham
Copy link
Collaborator

Aren't the two functionally equivalent, satyr? Either way, arguments.length is 0 and $ is defined only within the function scope, right?

@satyr
Copy link
Collaborator Author

satyr commented Oct 24, 2010

They behave differently in the presence of $ in upper scopes.

$ coffee -bpe '$ = 1; do -> $ = jQuery; blah'
var $;
$ = 1;
(function() {
  $ = jQuery;
  return blah;
})();

$ coffee -bpe '$ = 1; do ($) -> $ = jQuery; blah'
var $;
$ = 1;
(function($) {
  $ = jQuery;
  return blah;
})();

@satyr
Copy link
Collaborator Author

satyr commented Oct 25, 2010

 for value, index in array then do (value, index) ->

That's kind of a different issue--this issue is about removing parens.
And even with the magic, it still requires you to write variable names twice--not optimal. You'd rather have:

for var value, index in array ...

or something with the same semantics.

@hen-x
Copy link

hen-x commented Oct 25, 2010

What if we defined do to behave like this?
do = (args..., block) -> block args...
Then, it would be possible to pass arguments to the block like this:
for value, index in array then do value, index, (v, i) ->
It's verbose, but at least it doesn't overload the meaning of (value, index) ->.

@satyr
Copy link
Collaborator Author

satyr commented Oct 25, 2010

do = (args..., block) -> block args...

I'd worry that it'd be needlessly comlicated:

do a(), b(), (c())(_ref = a(), _ref2 = b(), (c())(_ref, _ref2))

with only small improvement from:

for value, index in array then do (v, i) -> v = value; i = index

@StanAngeloff
Copy link
Contributor

I like sethaurus approach because you can already do it in Coffee without adding new stuff to the core.

local = (args..., block) -> block args...
for i in [1..2] then local i, (i) -> console.log i

-1

@satyr
Copy link
Collaborator Author

satyr commented Oct 25, 2010

you can already do it in Coffee without adding new stuff to the core

Right. Core stuff should be simple.

@jashkenas
Copy link
Owner

So is the consensus that you can do it with a helper function instead of the do keyword?

@satyr
Copy link
Collaborator Author

satyr commented Oct 25, 2010

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
Copy link
Collaborator

zmthy commented Oct 25, 2010

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
Copy link

karl commented Oct 25, 2010

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
Copy link
Collaborator

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
Copy link
Collaborator Author

satyr commented Oct 25, 2010

do (x = y) -> x()

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

@TrevorBurnham
Copy link
Collaborator

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
Copy link
Collaborator Author

satyr commented Oct 26, 2010

@satyr
Copy link
Collaborator Author

satyr commented Oct 26, 2010

do (x = y) -> x()

Now possible in combination of my defarg branch.

@StanAngeloff
Copy link
Contributor

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
Copy link
Collaborator Author

satyr commented Oct 27, 2010

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
Copy link
Collaborator

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
Copy link
Contributor

< 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
Copy link
Contributor

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
Copy link
Collaborator Author

satyr commented Oct 28, 2010

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
Copy link
Collaborator

@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
Copy link

Svoloch commented Oct 30, 2010

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
Copy link
Owner

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

  • do is never more efficient than creating a nicely-named local variable to hold the function.
  • do is easily replaced by a helper function, without having to add anything to core.
  • do interacts strangely with => and ->.
  • For do to justify taking a keyword, it would need to be meaningfully better or more compact than the alternatives, and it doesn't pass that hurdle.

@naturalethic
Copy link

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
Copy link
Contributor

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
Copy link
Collaborator Author

satyr commented Nov 3, 2010

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
Copy link
Owner

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

@jashkenas
Copy link
Owner

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

@TrevorBurnham
Copy link
Collaborator

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

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

No branches or pull requests