-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Inconsistent behaviour of for loop/comprehension variable #728
Comments
Maybe a bit more realistic example:
Again, I understand why it works this way but isn't it confusing. |
akva: did you run into this in production code? You're absolutely right that this is the artifact from issue #423, but personally, I think that the benefits outweigh the downsides ... what do other folks think? |
One more artifact, #423 behaves differently in the presence of a return statement.
Here is my thoughts on this. JavaScript does not have a block scope, only function scope. It doesn't seem feasible to me to simulate block scope in a non leaky way. Doing so will break something, somewhere and sometimes in very subtle ways. It's true, the way functions close over loop variables always comes as a surprise when one encounter it for the first time. But once you've learnt this behaviour, it's actually very easy to understand and remember, since the semantics are very consistent. And it works this way in all major dynamic languages. Besides, as you pointed out in #423, there are easy workarounds in the form of
Not yet (because I don't use loop variables after the loop ;)), but let me ask you a counter question. How often do you really need to create closures inside a loop in production code? |
You need to create closures inside of a loop all the time ... when reading an array of files in Node.js, when firing ajax callbacks for a list of modified items in the browser, when mixing in method implementations from a third-party library... That was the original impetus for adding this feature -- it bites folks in JavaScript constantly. Here are two recent messages on the Node.js list from folks who are surprised by it: http://groups.google.com/group/nodejs/browse_thread/thread/cbd95f659e566b9c Our solution, despite being inconsistent, is the lesser of two evils, I think, as it allows for fast normal loops without block scope -- safe loops with block scope in any place where it could possibly matter, and there's an easy work around if you need to export a variable from within the inner block scope: set it to null before you start the loop. Agree? |
Well. This could be quite a typical client side event handling code
However, this code is quite inefficient. For production code one would rather write something like.
This code is more efficient, doesn't give any surprises, and arguably more modular - you can factor out the event handler function and use it elsewhere.
The best solution in this thread is actually the last post by Matt Ranney - use batch API. Batch style especially works well for ajax; you don't wanna fire ajax requests in a loop. Now, of course there are valid use cases when closing over a loop variable is necessary. I just guess they are not (or shouldn't be) as common as it seems. One more objection against #423 would be this. It teaches beginners the wrong thing and gives them false expectations.
People who get used to the #423 behaviour, without knowing the implementation details, may expect the above code to produce 1. |
We could have it both ways, and make loop bodies both capture and leak their variables when they contain an explicit closure. Consider: |
+1 for removing this kind of magic.
should be written as:
|
I know that we've all learned to avoid this bugaboo in JavaScript -- but it's an extremely common one. Beginners try to generate functions in for loops all the time, and are quickly surprised by JS behavior. What do folks think of sethaurus's proposal to purposefully leak assigned variables to outer scope, after the closure wrapper? |
Does it make sense to do the opposite, make ALL loop variables not visible outside the loop? |
I think sethaurus's proposal is the sanest solution. New scopes should only be created by the The one thing that gives me pause is that |
Original issue was #118. That takes me back. That issue never answered this problem - there's magic that isn't explicit, and therefore is often unexpected. Note that it's impossible to affect the variables defined in the loop from inside the scope, in case you want to control which indices to pull out beyond the ability of the |
The hairiest method in Coffeescript just got hairier. All variables assigned in a for loop now make their way outside of it, but each run-through gets its own index and value variables when enclosed functions are present. Closing the ticket. |
[Applause]... |
Broken with destructuring:
|
Given the concerns in #781 and how the implementation is still somewhat buggy, can we get rid of the implicit magic, and make it explicit instead with the |
No -- a special keyword for this is the worst of both worlds... We either should make it work transparently, or we should axe it and maintain JS semantics... |
Alright then. I'm torn on the issue, but magic it is. Closing this up, destructor bug fixed: 880c5c8 |
How about wrapping only relevant closures rather than the whole body? Before
After
|
I considered that, but decided it was too verbose. But passing them in as arguments was something I hadn't thought of. This is probably a better idea. |
Nice. It reminds me that we should move the |
Would a function that is instantly called be expected to modify the above values, though? |
Why would one want to do that? I'd rather forbid modification of loop variables than allow it. |
The point is that if someone chose to do this, the magic shouldn't be applied. And we can't tell if it's going to happen. |
What about the |
Or how about an independent mechanism that makes capturing easy. Like so:
↓
Then we could write:
|
Nah -- this case doesn't deserve new special syntax. It's either a feature that CoffeeScript provides -- because you never want to generate a function in a loop, without closing over the loop variables. Or, it's a place where we should give up the attempt, and just keep JS semantics... We can't correctly emulate the proposed behavior for |
There are cases where you want to use simple predicates in a loop:
which currently compiles to:
|
Excellent point. Let's kill the magic scoping. |
Does it mean #423 is being undone? |
@jashkenas thanks for the ping, sorry for the big delay. The original thought behind #423 was as a special case only for list comprehensions. E.g. a very narrow scope while you are generating a new list. However the coffeescript syntax for this is very similar to the general for loop case. (as opposed to something like python/javascript which wrap the comprehension in square brackets). You could still differentiate scoping based on whether the block comes before or after the for statement... but agreed, it could still be a source of confusion. |
As of 47fe5c2,
|
Good point about |
Closing this ticket, as #959 has now been fixed. |
So the semantics of the code following a comprehension depends on the comprehension's body. I understand why it's happening and I realize it's not a good practice to use loop variable after the loop anyway. But semantics should be clearly defined imho.
This behaviour seems to be the result of #423
The text was updated successfully, but these errors were encountered: