Closed satyr closed 13 years ago
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.
for l in elements setTimeout -> l.click()
We no longer have the magical scoping within for
.
. +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?
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 $
.
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.
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);
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
should just rescope existing variables
Probably. Then it becomes identical to the let
proposal (#728), only with a different keyword.
I think it's best for new scopes to always be indicated by ->
in CoffeeScript.
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?
Maybe:
do (v) ->
→ ((v) ->)()
do -> this
→ (-> this).call(this)
do -> arguments
→ (-> arguments).apply(this, arguments)
do (v) =>
→ ((v) ->).call(this, v)
Hard to imagine when (->)()
would be preferable over (->).call(this)
, though. Seems like a source of errors with little gain.
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
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.
Fair enough. So we just prevent use of =>
, then?
What about preserving arguments
?
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.
What do you mean by 'fake out'? If we directly pass the arguments
object all of the associated properties will be there.
Do you mean that arguments.callee
should refer to the new scope?
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 :)
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.
Done and done. Nothing fancy, and shadows existing variables as expected. For example: do (a, b) => becomes (function(a, b) {}).call(this, a, b);
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.
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.
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
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.
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.
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.
do () -> $ = jQuery
do ($) ->
$ = jQuery
Aren't the two functionally equivalent, satyr? Either way, arguments.length
is 0 and $
is defined only within the function scope, right?
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;
})();
Ah, yes, quite right. Corrected, thanks.
Wait, what happened to the shadowing with ->
? I thought that was the main advantage of this feature.
what happened to the shadowing with
->
?
It's there. Exactly what I initially proposed.
for l in elements then do (lmn) ->
lmn = l
setTimeout -> lmn.click()
That's not shadowing. There's not even any reason to pass in an argument unless you have another variable of the same name floating about. The reason I saw this as useful was that you could rescope existing variables.
The reason I saw this as useful was that you could rescope existing variables.
do =>
for ya.
Let's revert this, push out a point release, and we can keep talking about it.
Let's revert this
Why? It's purely additive--doesn't hurt any user upgrading from older versions.
I don't understand why the two should act differently.
do =>
can't introduce a new variable like do ->
can.
Why would you want to introduce a new variable? You've already created a new scope, you just need to assign to to it within that scope. It results in the same thing.
Why? It's purely additive--doesn't hurt anyone.
Adding unsettled features that are only half-baked, are still controversial, and are likely to still undergo change and/or be removed, does hurt people. If we push it out, folks start to depend on an unstable feature, and in the meantime it delays point releases. This is the sort of thing that belongs on a branch as it's being discussed. Reverted here:
http://github.com/jashkenas/coffee-script/commit/5b16d4790c54b63437039da890325e697ff25a49
Why would you want to introduce a new variable
For tighter variable scopes which can't be achived with CS's assignment system. Maybe read the issue title/body again?
In an IRC discussion on the reverted implementation, we didn't like the fact do func
doesn't work (inconsitency) and the magical scoping (confusion).
So the proposal is back at original; simple and primitive call:
do fn
→ fn()
do (x) ->
→ (function(x){})()
do (x) =>
→ (function(x){}).call(this)
, mirroring new
nicely. Opinions?
I still think that automatically passing variables in when a function is defined with the do
keyword is a good idea. For example:
for value, index in array then do (value, index) ->
...
is significantly cleaner than:
for value, index in array then do ->
val = value
i = index
...
You also end up with less variables over the place. It's a quick and tidy explicit solution to the now removed for
magic.
I don't see any reason to pass a function declaration with variables to the do
keyword unless this was the case. I don't agree with satyr's argument that it would enclose possibly nonexistent variables, just in case. The whole point of why we don't currently support shadowing is because you should be coding well enough to not need it for 'just in case' scenarios, and having do
should allow you to group your variables into scopes anyway, preventing any such problem from arising.
The whole point of bringing the discussion back out here was to get the opinion of everyone involved, though. Let's hear who prefers what, then make a decision.
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.
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) ->
.
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
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
you can already do it in Coffee without adding new stuff to the core
Right. Core stuff should be simple.
So is the consensus that you can do it with a helper function instead of the do
keyword?
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.