Re: lexical for-in/for-of loose end
Hi Allen, On Mon, 2012-02-06 at 11:08 -0800, Allen Wirfs-Brock wrote: We're putting a lot of energy into trying to figure out how to fix for(let;;) when it probably shouldn't even be the preferred form of numeric looping going forward. If we can make for-in/for-of attractive enough then it is less clear why we need to fix for(;;) [...] Maybe don't even add let/const forms to for(;;). Just as food for thought, here's a C# designer on why they decided to leave for (int i=0; iN; i++) alone, when they decided to make for (int i in L) bind a fresh i: We have this same problem in for blocks, but for blocks are much looser about what the loop variable is; there can be more than one variable declared in the for loop header, it can be incremented in odd ways, and it seems implausible that people would consider each iteration of the for loop to contain a fresh crop of variables. When you say for(int i; i 10; i += 1) it seems dead obvious that the i += 1 means increment the loop variable and that there is one loop variable for the whole loop, not a new fresh variable i every time through! We certainly would not make this proposed change apply to for loops. http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx Regards, Andy ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich wrote: Grant Husbands wrote: * Both of them make closures in the init and in the rest see a consistent world, for as long as is possible, while still giving body closures unique copies per iteration. Is there any inconsistency observable after as long as possible? I can't find one. It is a question if it is inconsistency, but init-closures called from inside body-closures have different impact when called while body-closure is live and after it is detached. The from-live call works the actual variables, which the caller actually uses, but in the from-detached call, they work with the past-the-loop loop-context/last-iter-context variables, while the body-closure has its own local copies. /be Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: With body-swap it concerns me a bit that is an arbitrary and variable number of closures may need to be updated each iteration (consider a body containing a while loop that spits out closures that capture init bindings.) I think there are some lazy possibilities for that. Create the copy beforehand by sort-of Object.create(loopContext) then either use the transparent proxy to loopContext while running / iterEnd: populate copy with let-vars / proxy become: copy (pity there probably is not #become: (or #forwardBecome:) in vm) or store copy to @wouldBeCopy in loopContext / closure-created: store copy to [[ContextForDetachment]] in closure / use actual record (loop-context) since actual-record-to-use.@wouldBeCopy === [[ContextForDetachment]] / iterEnd: populate copy with let-vars loopContext.@wouldBeCopy = null (later different object in subsequent iterations) / in detached closure, lazily do actual-context-to-use = [[ContextForDetachment]] since actual-record-to-use.@wouldBeCopy exists and is different from [[ContextForDetachment]]. Implementors can probably find better ones. Allen Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 6 February 2012 21:03, Herby Vojčík he...@mailbox.sk wrote: Well, in fact what I proposed was not changing the behaviour of head-closures, but body-closures. Basically, the idea was: let the loop run in legacy semantics (only one block for the whole for-loop). All closures (head ones and body ones) access the same let-var and modify it. But, for at the end of each iteration, if body-closures were created, rebind that closures to copy (or, better, child) of actual execution record with let-var being local (de-facto simulating per-iteration binding, but lazily; and only for async closures). What are the closures that were created, and how do you keep track of them? What should the following do? let a = [], b = [], c = [], d = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; b[i] = f(); c[i] = function(){ return i } } print(c[2]()); print(d[2]()) a[4]()(); print(c[4]()); print(d[4]()) b[7](); print(c[7]()); print(d[7]()) And imagine the fun you can have with generators! Spec-wise, I think it only needs to define two things, CopyExecutionRecord, which Grant already mentioned, or something similar like CopyWithFixedVars(ExecutionRecord, vars-to-fix) and ReplaceExecutionRecordBinding to allow closure(s) bound to original execution record re-bind it to its copy created by the former. Allow me to be blunt: this is literally raping the concepts of declarative environment and lexical closure. It is a hack, completely non-orthogonal, and I predict that the hidden statefulness it introduces to closure environments would be going to bite back in gore proportions. Seriously, before we consider going there, I'd rather have a step on the break and stick to C-style for-semantics. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
RE: lexical for-in/for-of loose end
This seems like a highly compelling argument. I hope I'm not the only one who thinks the existing behavior of `for(;;)` makes sense. Granted, that comes from understanding the detail that closures close over variables and not values, which most users will not. But in general, `for(;;)` makes it very clear that there's a single mutating variable, which the desugarings proposed here seem to overcomplicate and contradict. As exemplified by the comments to Eric's blog post, the current behavior is not very astonishing to many developers, and once it's explained once, becomes intuitive. (Yes, sampling bias, but still.) The question is whether saving that pedagogic burden is worthwhile. I argue that it isn't. `for(;;)` is a low-level construct even in ES5, where `Array.prototype.forEach` supplants it for the most part. It doesn't have to do what I mean; it should do what I say. Introducing an extra set of curly braces around the loop so that my `let` loop variables don't get hoisted to the outer scope is the most work that makes sense to me. -Domenic -Original Message- From: es-discuss-boun...@mozilla.org [mailto:es-discuss-boun...@mozilla.org] On Behalf Of Andy Wingo Sent: Tuesday, February 07, 2012 5:15 To: es-discuss@mozilla.org Subject: Re: lexical for-in/for-of loose end Hi Allen, On Mon, 2012-02-06 at 11:08 -0800, Allen Wirfs-Brock wrote: We're putting a lot of energy into trying to figure out how to fix for(let;;) when it probably shouldn't even be the preferred form of numeric looping going forward. If we can make for-in/for-of attractive enough then it is less clear why we need to fix for(;;) [...] Maybe don't even add let/const forms to for(;;). Just as food for thought, here's a C# designer on why they decided to leave for (int i=0; iN; i++) alone, when they decided to make for (int i in L) bind a fresh i: We have this same problem in for blocks, but for blocks are much looser about what the loop variable is; there can be more than one variable declared in the for loop header, it can be incremented in odd ways, and it seems implausible that people would consider each iteration of the for loop to contain a fresh crop of variables. When you say for(int i; i 10; i += 1) it seems dead obvious that the i += 1 means increment the loop variable and that there is one loop variable for the whole loop, not a new fresh variable i every time through! We certainly would not make this proposed change apply to for loops. http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx Regards, Andy ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 7 February 2012 13:32, Andreas Rossberg rossb...@google.com wrote: What are the closures that were created, They are the closures that lie lexically within the loop's code, outside of the init, (or, put differently, those that have the loop's environment record in scope) and that were instantiated in the current iteration. and how do you keep track of them? In a linked list or array. Somewhere that sits with the loop state. In desugaring terms, it could be expressed as an array, perhaps of weak pointers, in the outer loop scope. I haven't got up to speed on Chez Scheme's closures enough to answer this question for those. What should the following do? let a = [], b = [], c = [], d = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; b[i] = f(); c[i] = function(){ return i } } print(c[2]()); print(d[2]()) a[4]()(); print(c[4]()); print(d[4]()) b[7](); print(c[7]()); print(d[7]()) Here's what it does under the proposed scheme: c[2]() - 2 d[2]() - 3 a[4]()() increments the i that is 10 to 11. c[4]() - 4 d[4]() - 5 b[7]() increments the i that is now 11 to 12. c[7]() - 7 d[7]() - 8 Basically, the closures lexically within the loop init always see the latest version of i. Those lexically within the rest of the loop always see the i that was available at the end of their iteration. The iteration part of the loop is considered to be at the start. A related gotcha is that d[9]() would return 12, if added to the end of that sequence of calls. (I'm answering to the best of my ability, not trying to defend anything.) And imagine the fun you can have with generators! Indeed, you'd need to alter the [[ExecutionContext]] of suspended generators whose [[Code]] lies within the loop (as well as altering the [[Scope]]), which may be a bigger deal than just modifying [[Scope]]s. Allow me to be blunt: this is literally raping the concepts of declarative environment and lexical closure. It is a hack, completely non-orthogonal, I think that may be more than blunt; it is strongly emotive and also vague enough that it can't really be answered. However, I and others do share the concern that it may introduce too much complexity, and then only really add support for a very rare usage. and I predict that the hidden statefulness it introduces to closure environments would be going to bite back in gore proportions. It's a possibility, indeed. The variant that alters only the closures lexically within the loop init has less chance of that, and could potentially have restriction imposed, but all of these variants do indeed introduce a way of (slightly) modifying the scope of a particular group of closures after they have been created. Seriously, before we consider going there, I'd rather have a step on the break and stick to C-style for-semantics. I think we wouldn't need to go that far; your generalization of Mark's desugaring covers plenty of use cases and certainly the one of most common loop/closure gotchas. To my understanding, that's the favoured idea, so far, and we're just exploring this one to see where it might lead. Regards, Grant Husbands. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 7 February 2012 15:40, Grant Husbands esdisc...@grant.x43.net wrote: On 7 February 2012 13:32, Andreas Rossberg rossb...@google.com wrote: What should the following do? let a = [], b = [], c = [], d = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; b[i] = f(); c[i] = function(){ return i } } print(c[2]()); print(d[2]()) a[4]()(); print(c[4]()); print(d[4]()) b[7](); print(c[7]()); print(d[7]()) Here's what it does under the proposed scheme: c[2]() - 2 d[2]() - 3 a[4]()() increments the i that is 10 to 11. c[4]() - 4 d[4]() - 5 b[7]() increments the i that is now 11 to 12. c[7]() - 7 d[7]() - 8 Basically, the closures lexically within the loop init always see the latest version of i. Those lexically within the rest of the loop always see the i that was available at the end of their iteration. The iteration part of the loop is considered to be at the start. A related gotcha is that d[9]() would return 12, if added to the end of that sequence of calls. (I'm answering to the best of my ability, not trying to defend anything.) But note that the environment in b's closures is not the for-loop environment. Instead, it is a local environment, whose parent is the for-loop environment. To make that case work, it is not enough to be able to modify the [[Scope]] environment of closures -- in general, you'd need to swap out arbitrary parts of an environment chain. Allow me to be blunt: this is literally raping the concepts of declarative environment and lexical closure. It is a hack, completely non-orthogonal, I think that may be more than blunt; it is strongly emotive and also vague enough that it can't really be answered. However, I and others do share the concern that it may introduce too much complexity, and then only really add support for a very rare usage. You are right, I should have been more careful. I am sorry about that. This is not merely a question of complexity, though. It's more fundamental. Environments are immutable mappings from names to locations -- that's a basic axiom of lexical scoping. Breaking it will have unforeseeable consequences. That may be vague, I agree, but I prefer not to find out concretely. :) Subtle combinations of higher-orderness and state rarely let you down in terms of nasty surprises. Seriously, before we consider going there, I'd rather have a step on the break and stick to C-style for-semantics. I think we wouldn't need to go that far; your generalization of Mark's desugaring covers plenty of use cases and certainly the one of most common loop/closure gotchas. To my understanding, that's the favoured idea, so far, and we're just exploring this one to see where it might lead. I am fine with either. So far I have favoured fresh-variables-per-iteration, but the current discussion has raised some doubts in the back of my mind. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Explicit local copies (was: Re: lexical for-in/for-of loose end)
Andreas Rossberg wrote: let a = [], b = [], c = [], d = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; b[i] = f(); c[i] = function(){ return i } } But note that the environment in b's closures is not the for-loop environment. Instead, it is a local environment, whose parent is the for-loop environment. To make that case work, it is not enough to be able to modify the [[Scope]] environment of closures -- in general, you'd need to swap out arbitrary parts of an environment chain. Oww, that's a problem. Copies are hard here... refs can do it, but no one likes them. I am fine with either. So far I have favoured fresh-variables-per-iteration, but the current discussion has raised some doubts in the back of my mind. This whole discussion leads me to completely different opinion: what about to embrace explicit is better than implicit for this case, and allow let/const without initializer to create local copy of an outer variable? Then, if you need to capture a value, you write it down (I know this appeared to avoid capturing by hand in (function (i) { ... })(i), but reusing normal blocks to do it may work, wouldn't it? It disallows some of the ninjutsu (init-closures will work with different i than localized closures), but it will be clearly visible: let a = [], b = [], c = [], d = [], e = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; // this b[i] = f(); // and this c[i] = function(){ return i } // and this work with loop-level i { let i; e[i] = function () { return i; } // this has local i } } /Andreas There can be even shorter syntax, like e[i] = function () let i { return i; } // this has local i or e[i] = function () { let i; return i; } // this has local i whoch can avoid need of {} block, but it must be thought of if it is possible consistently (the former should, the latter may be questionable). Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Andreas Rossberg wrote: let a = [], b = [], c = [], d = [] for (int i = 0, f = function(){ return function() { ++i } }; i 10; d[i] = function() { return i }, ++i) { a[i] = f; b[i] = f(); c[i] = function(){ return i } } But note that the environment in b's closures is not the for-loop environment. Instead, it is a local environment, whose parent is the for-loop environment. To make that case work, it is not enough to be able to modify the [[Scope]] environment of closures -- in general, you'd need to swap out arbitrary parts of an environment chain. Indeed. I specified that operation as a recursive, constructive operation, in something like spec-lingo, in one of my emails, and pointed out that it is likely to introduce an extra indirection in most JS engines, for at least some scopes, due to its assumptions about envRec; I noted (and Brendan agreed) that such could be a major problem. To recap, it looked like this (this time writing it as JS): function ReplaceEnvInEnv(E, C) { if (E==C) return new Env(E.outer, E.envRec.clone()); else return new Env(ReplaceEnvInEnv(E.outer, C), E.envRec); } (You would pass it the [[Scope]] of a closure in E and the loop iteration scope in C and it would give you a new [[Scope]] for that closure.) I'm hoping that someone knowledgeable about Chez Scheme's flat closures will let us know about the similar detail of that and the pitfalls. This is not merely a question of complexity, though. It's more fundamental. Environments are immutable mappings from names to locations -- that's a basic axiom of lexical scoping. Well, lexical scoping is scoping in which names refer to (more or less) the local lexical environment. That is still true under the proposal. It might indeed be desirable for the mapping to be immutable, but I don't think that's a given. The above recursive design does not require direct alteration of mappings. Breaking it will have unforeseeable consequences. I'd agree with a phrasing like Breaking it may have unforeseen consequences. That may be vague, I agree, but I prefer not to find out concretely. :) Subtle combinations of higher-orderness and state rarely let you down in terms of nasty surprises. I agree that this proposal may run up against some of the core values and idioms of javascript and may want to be rejected for that reason; in fact, I would probably currently vote against it, if I were asked to participate in a vote. However, I'll continue to try to make sure we're all on the same page as regards the potential design details and workarounds, and hope not to bother people too much. Regards, Grant. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 4 February 2012 21:55, Brendan Eich bren...@mozilla.org wrote: The argument is as follows: for (let i = 0, a = some.array, n = a.length; i n; i++) { ... } here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. Now consider a bit of eta conversion: for (let i = 0, a = some.array, n = (function(){return a})().length; i n; i++) { ... } Nit: That is a beta-conversion, not an eta-conversion. ;-) (Fortunately, because eta-conversions are not actually semantics-preserving in an impure language.) I claim implementation is not the driver here. User expectations, esp. savvy users who might make some practical or theoretical (testing) use of eta conversion, matter more. Agreed. As long as we don't spec something weird, the extra effort for implementations shouldn't be much more than that of an extra block around the loop body. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 4 February 2012 18:49, Brendan Eich bren...@mozilla.org wrote: I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. This requires unrolling the loop once. Let's see how the desugaring from: for (let d1 = e1, ... dN = eN; cond; update) { body; } looks. It doesn't seem terrible: $loopEnd: { let d1 = e1, ... dN = eN; if (cond) { body; update; const $loop = { |d1, ... dN| if (!cond) break $loopEnd; body; update; $loop(d1, ... dN); } $loop(d1, ... dN); } } I'm at a loss what this is trying to achieve. Modifications of the loop variables by the loop _body_ will be visible to future iterations if they happen in the first, but not if they happen in any consecutive iteration? That seems odd. What is the use case for this, and why should we support it as a special case? In other words, why isn't the following good enough? { let d1 = e1, ..., dN = eN; const $loop = { |d1, ..., dN| if (cond) { body update; $loop(d1, ..., dN); } } $loop(d1, ..., dN); } FWIW, this is simply the generalization of Mark's desugaring so that it works with destructuring, multiple bindings, and recursive bindings. Plus, it removes the redundant second lambda in his version. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Andreas Rossberg wrote: On 4 February 2012 21:55, Brendan Eichbren...@mozilla.org wrote: The argument is as follows: for (let i = 0, a = some.array, n = a.length; i n; i++) { ... } here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. Now consider a bit of eta conversion: for (let i = 0, a = some.array, n = (function(){return a})().length; i n; i++) { ... } Nit: That is a beta-conversion, not an eta-conversion.;-) (Fortunately, because eta-conversions are not actually semantics-preserving in an impure language.) (B-key was sticky :-P) Thanks... I claim implementation is not the driver here. User expectations, esp. savvy users who might make some practical or theoretical (testing) use of [beta] conversion, matter more. Agreed. As long as we don't spec something weird, the extra effort for implementations shouldn't be much more than that of an extra block around the loop body. To take Allen's best shot and re-fire it, what do you think should happen here? for (let i = 0, skip2 = function(){i++}; i N; i++) { foo(); if (bar()) skip2(); } /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Andreas Rossberg wrote: On 4 February 2012 18:49, Brendan Eichbren...@mozilla.org wrote: In other words, why isn't the following good enough? { let d1 = e1, ..., dN = eN; const $loop = {|d1, ..., dN| if (cond) { body update; $loop(d1, ..., dN); } } $loop(d1, ..., dN); } This is the 0th iteration scope idea. It's good enough if (a) we want skip2 (see last post) affecting no iteration's loop variables, and (b) we can take the extra scope cost-hit. FWIW, this is simply the generalization of Mark's desugaring so that it works with destructuring, multiple bindings, and recursive bindings. Plus, it removes the redundant second lambda in his version. It's fine if we like (a) on usability grounds, and (b) is true for all implementors and wtf-benchmarketing kooks. I argued vigorously for 1st-iteration rather than 0th-iteration scope but that was to say no to (b). I'm actually not sure what skip2-coding authors will want -- probably an error as Allen suggests. But let's say yes to (a) -- I can live with (b) absent further evidence. The issue remains (a). Allen's best shot is the skip2 scenario (advance in his post). What's the right outcome for users who write such code? /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 6 February 2012 16:28, Brendan Eich bren...@mozilla.org wrote: Andreas Rossberg wrote: Agreed. As long as we don't spec something weird, the extra effort for implementations shouldn't be much more than that of an extra block around the loop body. To take Allen's best shot and re-fire it, what do you think should happen here? for (let i = 0, skip2 = function(){i++}; i N; i++) { foo(); if (bar()) skip2(); } Pretty much the same as in let i_ = 0, skip2_ = function(){i_++}; for (; i_ N; i_++) { let i = i_, skip2 = skip2_; foo(); if (bar()) skip2(); } Probably not what the programmer expected, but as others have said, it's a trade-off. This use case seems rare, rather obfuscated style IMHO, and you can easily work around it. So probably not something you want to be optimizing the language semantics for. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Andreas Rossberg wrote: On 6 February 2012 16:28, Brendan Eichbren...@mozilla.org wrote: Andreas Rossberg wrote: Agreed. As long as we don't spec something weird, the extra effort for implementations shouldn't be much more than that of an extra block around the loop body. To take Allen's best shot and re-fire it, what do you think should happen here? for (let i = 0, skip2 = function(){i++}; i N; i++) { foo(); if (bar()) skip2(); } Pretty much the same as in let i_ = 0, skip2_ = function(){i_++}; for (; i_ N; i_++) { let i = i_, skip2 = skip2_; foo(); if (bar()) skip2(); } Yes, this is something Jon Zeppieri brought up as a reason not to do fresh-binding-per-iteration in for(let;;) -- moving the initializer part out of the loop loses the binding-per-iteration. You've shown b-p-i restored manually, with i_ vs. i renaming, but in the likely scenarios the user sees only one i variable. Try this version: let i = 0, skip2_ = function(){i++}; for (; i N; i++) { foo(); if (bar()) skip2(); } This must work (skip2 must advance the one i binding; note no closure capture here) just as it does today. Jon was arguing against a change in binding semantics simply due to moving the let into the for-head. We who were in the TC39 meeting room late the second day have overcome Jon's good counter-argument. For the greater good on balance, many of us TC39ers want to make for(let;;) a special form, so closure capture works as most users expect. You're right that the trade-off where skip2 fails to update any useful loop variable (or the first iteration's loop variable only -- agreed that is odd) follows from this greater-good choice. But could we do better? Making dynamic errors out of skip2-like capture seems bad. We can't guarantee early errors. Probably best to avoid adding error cases... Probably not what the programmer expected, but as others have said, it's a trade-off. This use case seems rare, Agreed on rare. rather obfuscated style This isn't so clear to me, since the case where the let is manually hoisteid from the for head does work, and hackers who learned C or C++ may think that moving the let into the head is just better style, not obfuscatory. IMHO, and you can easily work around it. So probably not something you want to be optimizing the language semantics for. I agree with you, but feel a small nagging doubt. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich wrote: [Grant Husbands wrote:] 'Note' all closures (dynamically) created in (lexically) the loop initializer. Only in the initializer? Why should closures formed there be dynamically scoped to the current iteration? Because that directly serves the use cases that have for-initialization-based closures modifying loop variables. None of the simple desugarings do that, beyond perhaps the first iteration. Instead [this requires] a dynamic scope for closures in the initializer [...] Dynamic scope is a warning sign, almost always a mistake. Indeed, given the eval shape-changing problem you pointed out, this cannot be properly resolved, in the first form of this proposal. The second form might not require generalised dynamic scope, though; it requires an operation that clones a scope with a single environment record changed, though. More on that, later. Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. This is a more complex spec than one that models each iteration having its own lexical scope. The spec needs only declarative environments, not hidden references and pointer updates. I think there may be a misunderstanding, as the operation I'm talking about can be given in spec language without talk of references and pointer updates. I'll write it out at the end of this email. As an implementation technique, Chez Scheme's heap boxing and assignment conversion could be even better. Indeed, and that introduction of a level of indirection for affected variables is what Herby was effectively suggesting, if I'm understanding you both correctly. I was trying to suggest something I thought was more compatible with the current specification. But this is all beyond the spec. I don't think it is. What Herby's idea and these formulations present is a way for let-based loops to have modifications in closures captured in the for-head that alter the loop variables in a way that's visible to the current loop iteration. As such, choosing whether or not to use these formulations affects the spec. I agree that it may indeed be too large a feature, given that desugarings can cover the vast majority of use-cases well enough. I just thought it worth following the logic through. Still trying to be sure you intended a unique and dynamic scope for the initializer (first part) of for(let;;). In the first version, depending on the definition of dynamic, yes. In the second version, no, though instead closures inside the loop body get a similarly 'dynamic' scope. Here's a longer, still informal version of the second. Given a loop of this form: for (let i = 0, inc = function(){i++}; iN; inc()) { ... } We want the loop to run to completion while ensuring that each closure within the body gets a copy of 'i' as it existed in the loop iteration in which the closure was created. One way to do that is to keep track of the closures that get created during a loop iteration and, at the end of the loop iteration, give those closures a new environment ([[Scope]]) which is a copy of the old one, but with a clone of the current loop body environment record replacing the original. In terms of spec: The 'add the closure to a list' operation could be described using a desugaring. The 'scope clone with one record changed' operation and the end-of-iteration use of it would require spec changes. In spec language, the main operation would probably be like this (I'm cargo-culting the format, though, so I apologise if it has idiosyncrasies): 10.2.2.9 CopyDeclarativeEnvironment (E, C) When the abstract operation CopyDeclarativeEnvironment is called with a Lexical Environment as argument E and a Lexical Environment as argument C the following steps are performed: 1. Let env be a new Lexical Environment. 2. Let lastOuter be the outer lexical environment of E 3. If E and C are the same: a. Let envRec be a copy of the environment record of E. b. Set env's environment to be envRec c. Set the outer lexical environment reference of env to lastOuter 4. Else a. Set the environment record of env to be the environment record of E b. Let outer be the result of calling CopyDeclarativeEnvironment passing lastOuter and C as the arguments c. Set the outer lexical environment reference of env to be outer 5. Return env. Probably the biggest issue with the above spec is that it assumes envRec inside an environment is reference-like, which it could easily not be in current implementations, so this variant would also introduce an extra indirection in at least some circumstances. I support the two main destructurings under consideration right now more than I support the above, though. Regards, Grant. ___ es-discuss mailing list
Re: lexical for-in/for-of loose end
On Feb 6, 2012, at 7:59 AM, Brendan Eich wrote: Andreas Rossberg wrote: ... Yes, this is something Jon Zeppieri brought up as a reason not to do fresh-binding-per-iteration in for(let;;) -- moving the initializer part out of the loop loses the binding-per-iteration. You've shown b-p-i restored manually, with i_ vs. i renaming, but in the likely scenarios the user sees only one i variable. Try this version: let i = 0, skip2_ = function(){i++}; for (; i N; i++) { foo(); if (bar()) skip2(); } This must work (skip2 must advance the one i binding; note no closure capture here) just as it does today. Jon was arguing against a change in binding semantics simply due to moving the let into the for-head. We who were in the TC39 meeting room late the second day have overcome Jon's good counter-argument. For the greater good on balance, many of us TC39ers want to make for(let;;) a special form, so closure capture works as most users expect. You're right that the trade-off where skip2 fails to update any useful loop variable (or the first iteration's loop variable only -- agreed that is odd) follows from this greater-good choice. But could we do better? Making dynamic errors out of skip2-like capture seems bad. We can't guarantee early errors. Probably best to avoid adding error cases... I probably could be made an early error. Whether or not that would be a good things is a different issue. Probably not what the programmer expected, but as others have said, it's a trade-off. This use case seems rare, Agreed on rare. rather obfuscated style This isn't so clear to me, since the case where the let is manually hoisteid from the for head does work, and hackers who learned C or C++ may think that moving the let into the head is just better style, not obfuscatory. Also, while it doesn't make a lot of sense for i++ it might if the actual skip code code was a complex expression containing long names or if it occurs at multiple places in the loop body. IMHO, and you can easily work around it. So probably not something you want to be optimizing the language semantics for. I agree with you, but feel a small nagging doubt. We're putting a lot of energy into trying to figure out how to fix for(let;;) when it probably shouldn't even be the preferred form of numeric looping going forward. If we can make for-in/for-of attractive enough then it is less clear why we need to fix for(;;) for (var i=0, nn; I++) capture(function () {i}); still have to be modified to for (let i=0, nn; I++) capture(function () {i}); to fix the capture bug in existing code. Why not make the new better way something like for (let i of range(0,n)) capture(function () {i}); and promote the hell out of it while leavingfor(;;) as is. Maybe don't even add let/const forms to for(;;). Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Grant Husbands wrote: But this is all beyond the spec. I don't think it is. What Herby's idea and these formulations present is a way for let-based loops to have modifications in closures captured in the for-head that alter the loop variables in a way that's visible to the current loop iteration. And read back changes from the current iteration. Yes, I see that a new (novel!) observable semantic model is being proposed here, not an optimization. At first it seemed Herby was proposing just the usual optimizations. I still do not think it's wise to specify in terms of such pointer-updating reference semantics, not for the body closures that want to capture loop control variables. But only if closures in the for-head capture loop variables? That would be Allen's DWIM (Do What I mean) semantics. DWIM is tempting. Perl is full of it and it can be convenient at first. It often blows back, badly, due to ambiguity. As such, choosing whether or not to use these formulations affects the spec. Agreed, with caution about bending the body closure model around this prematurely. If we pull it off, probably the body closures can do the same optimization -- but it's not clear we can pull it off. I agree that it may indeed be too large a feature, given that desugarings can cover the vast majority of use-cases well enough. I just thought it worth following the logic through. Gotcha. It is tempting to try for this, since it would let a C++-knowing hacker write a loop like the one you show below, and have it work -- while the common var-capture pigeon-hole problem would be solved too for let-bindings captured by body-closures. Still trying to be sure you intended a unique and dynamic scope for the initializer (first part) of for(let;;). In the first version, depending on the definition of dynamic, yes. In the second version, no, though instead closures inside the loop body get a similarly 'dynamic' scope. Here's a longer, still informal version of the second. Given a loop of this form: for (let i = 0, inc = function(){i++}; iN; inc()) { ... } We want the loop to run to completion while ensuring that each closure within the body gets a copy of 'i' as it existed in the loop iteration in which the closure was created. One way to do that is to keep track of the closures that get created during a loop iteration and, at the end of the loop iteration, give those closures a new environment ([[Scope]]) which is a copy of the old one, but with a clone of the current loop body environment record replacing the original. [snip] Probably the biggest issue with the above spec is that it assumes envRec inside an environment is reference-like, which it could easily not be in current implementations, so this variant would also introduce an extra indirection in at least some circumstances. This is a big issue, I agree. I support the two main destructurings under consideration right now more than I support the above, though. Any preference for 0th over 1st iteration scope for closures in INIT binding initializer expressions? You and Allen remind me that the whole head needs thought: for (INIT; TEST; NEXT) BODY If we do what Andreas suggests, 0th iteration scope for INIT, then TEST must use BODY scope for the iteration that follows if TEST evaluates truthy. And NEXT must use a new scope for the next iteration too, preparing for TEST. enterblock V INIT reenterblock // Andreas's suggested 0th iteration scope for INIT goto L2 L1: BODY reenterblock NEXT L2: TEST iftrue L1 leaveblock This is not going to give TEST and NEXT the DWIM semantics. Sorry, I'm sure you get this, I'm just spelling it out to be sure everyone (including me) gets it. Is this a problem? I don't know. INIT may want the DWIM semantics, but TEST and NEXT before it for 2nd through Nth iterations really are more like parts of the BODY. Why should they form closures that magically reference current iteration scope when called later? DWIM always falls to ambiguity. What did you mean? I dunno, just do it! :-P /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: We're putting a lot of energy into trying to figure out how to fix for(let;;) when it probably shouldn't even be the preferred form of numeric looping going forward. If we can make for-in/for-of attractive enough then it is less clear why we need to fix for(;;) for (var i=0, nn; I++) capture(function () {i}); still have to be modified to for (let i=0, nn; I++) capture(function () {i}); to fix the capture bug in existing code. Why not make the new better way something like for (let i of range(0,n)) capture(function () {i}); I'm a big fan of iterators and this will be winning. Indeed many languages add a range operator for more convenience: for (let i of 0..n) ... or perhaps ... (yech). and promote the hell out of it while leavingfor(;;) as is. Here I demur. We can promote all we want. Won't necessarily do a thing to JS developers who see for(let;;) as a better way to write (or migrate, note well) code they wrote (or inherited, double note well) that uses for(var;;). Maybe don't even add let/const forms to for(;;). Again I dissent. We're not making a new language, so breaking symmetry to deprecate looks like nanny-ing. It usually fails, in my experience. It doesn't help migration. You make a good point: we should not overcomplicate for(let;;) at high cost. I'm happy with Andreas's proposed 0th-iter scope desugaring, modulo nagging doubts that are going away even as I type. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 6, 2012, at 11:24 AM, Brendan Eich wrote: You make a good point: we should not overcomplicate for(let;;) at high cost. I'm happy with Andreas's proposed 0th-iter scope desugaring, modulo nagging doubts that are going away even as I type. particularly, when somebody who really wants per loop bindings they can easily say: { let i; for (;in;i++; { ... } } Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich wrote: Grant Husbands wrote: But this is all beyond the spec. I don't think it is. What Herby's idea and these formulations present is a way for let-based loops to have modifications in closures captured in the for-head that alter the loop variables in a way that's visible to the current loop iteration. And read back changes from the current iteration. Yes, I see that a new (novel!) observable semantic model is being proposed here, not an optimization. At first it seemed Herby was proposing just the usual optimizations. Well, I hope I was understood. Even if not and something interesting sprang out, it's ok. I'll go into details later. I still do not think it's wise to specify in terms of such pointer-updating reference semantics, not for the body closures that want to capture loop control variables. But only if closures in the for-head capture loop variables? That would be Allen's DWIM (Do What I mean) semantics. DWIM is tempting. Perl is full of it and it can be convenient at first. It often blows back, badly, due to ambiguity. As such, choosing whether or not to use these formulations affects the spec. Agreed, with caution about bending the body closure model around this prematurely. If we pull it off, probably the body closures can do the same optimization -- but it's not clear we can pull it off. Well, in fact what I proposed was not changing the behaviour of head-closures, but body-closures. Basically, the idea was: let the loop run in legacy semantics (only one block for the whole for-loop). All closures (head ones and body ones) access the same let-var and modify it. But, for at the end of each iteration, if body-closures were created, rebind that closures to copy (or, better, child) of actual execution record with let-var being local (de-facto simulating per-iteration binding, but lazily; and only for async closures). Spec-wise, I think it only needs to define two things, CopyExecutionRecord, which Grant already mentioned, or something similar like CopyWithFixedVars(ExecutionRecord, vars-to-fix) and ReplaceExecutionRecordBinding to allow closure(s) bound to original execution record re-bind it to its copy created by the former. It's all. It solves this concern of Allen: In the case where there are no closure captures in the body we probably all expect implementations to actually share a single set of bindings across all iterations. If an implementation did that for this case it would yield the result the programmer to expecting. But if an implementation actually did that in this case it would be an invalid optimization because of the proposed bind to only first iteration semantics. With what I proposed, there is no 0th / 1st iteration semantics etc. since all the loop shares the binding through its whole run. ... Any preference for 0th over 1st iteration scope for closures in INIT binding initializer expressions? ... INIT may want the DWIM semantics, but TEST and NEXT before it for 2nd through Nth iterations really are more like parts of the BODY. Why should they form closures that magically reference current iteration scope when called later? I definitely think INIT should not have any special behaviour for 1st and for subsequent iterations. The semantics of the loop is then much more straightforward and less magical. So in case of these desugaring, I am for 0th iteration. To be able to read loop as { INIT; for (;TEST;NEXT) BODY }. DWIM always falls to ambiguity. What did you mean? I dunno, just do it! :-P /be Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Herby Vojčík wrote: I definitely think INIT should not have any special behaviour for 1st and for subsequent iterations. The semantics of the loop is then much more straightforward and less magical. So in case of these desugaring, I am for 0th iteration. To be able to read loop as { INIT; for (;TEST;NEXT) BODY }. Of course, with let vars bound to closure per-body... but what I mean conceptually draw a line between initialization and loop body, and making each loop body obey the same rules. Even if it means head-closure will have no effect. But it will not have it regularly, in first as well in next iterations. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Grant Husbands wrote: Now, there are some advantages and disadvantages: Thanks for expanding on your and Herby's proposals, it helps. A disadvantage on its face of Herby's init-swap is that variables are copied from iteration scope to iteration scope, *and* all contained closures need their [[Scope]] adjusted every iteration. For the code you evaluate Herby's body-swap against, the flat closure (Chez Scheme display closure) formation is indeed good optimization. The closure passed to setTimeout post-dominates the only live (from its point of view) assignments to i, so copying i's value into the closure allows very fast access and no scope entrainment at all. A harder case would mutate upvars after the closure is evaluated, so that it must capture references not values. In any case, the scope would need to be cloned on each iteration where closures formed, and those closures' [[Scope]] internal properties updated. Allen should comment on whether this attempt to square the circle (kidding, mostly) might fly in the spec. Implementors while secondary should weigh in too. * Herby's body-swap gives an apparent desugaring with just one scope for the loop variables Yes, that is tempting. Kudoes to you and Herby for pushing this. Great use of es-discuss. * Herby's init-swap might need to be concerned about shape changes (due to eval or such). Yes. * Both of them pretty much require some form of scope cloning or scope modification that does not match anything currently in the spec. This is another one for Allen. * Both of them make closures in the init and in the rest see a consistent world, for as long as is possible, while still giving body closures unique copies per iteration. Is there any inconsistency observable after as long as possible? I can't find one. Sorry for the long email, but I thought the detail could help iron out any of the remaining (lesser) misunderstandings. No need to apologize. My warnings about DWIM are general, and you and Herby have proposed specific solutions. Worth going over in detail. Thanks again, /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 6, 2012, at 5:32 PM, Brendan Eich wrote: Grant Husbands wrote: Now, there are some advantages and disadvantages: Thanks for expanding on your and Herby's proposals, it helps. A disadvantage on its face of Herby's init-swap is that variables are copied from iteration scope to iteration scope, *and* all contained closures need their [[Scope]] adjusted every iteration. For the code you evaluate Herby's body-swap against, the flat closure (Chez Scheme display closure) formation is indeed good optimization. The closure passed to setTimeout post-dominates the only live (from its point of view) assignments to i, so copying i's value into the closure allows very fast access and no scope entrainment at all. A harder case would mutate upvars after the closure is evaluated, so that it must capture references not values. In any case, the scope would need to be cloned on each iteration where closures formed, and those closures' [[Scope]] internal properties updated. Allen should comment on whether this attempt to square the circle (kidding, mostly) might fly in the spec. Implementors while secondary should weigh in too. My sense is that we could make these schemes works in spec. space. Either by allowing bindings that have References as values or by adding new mechanisms such environment cloning. It actually sounds fun and perhaps even the right thing to do. However, I have some doubts concerning whether the added work and spec. complexity is really justified by the benefit. Maybe, but there are lots seemingly more important things to work on. With body-swap it concerns me a bit that is an arbitrary and variable number of closures may need to be updated each iteration (consider a body containing a while loop that spits out closures that capture init bindings.) For both, I need to think about whether it may be possible (and the impact) for a closure activation (containing a generator?) to span a iteration boundary. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: For both, I need to think about whether it may be possible (and the impact) for a closure activation (containing a generator?) to span a iteration boundary. Generators close over their lexical environment, yes. And of course an active generator can survive invoking contexts such as a single loop iteration. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Grant Husbands wrote: Herby Vojčík wrote: It looks to me that all those schemes and desugaring are very complicated The full problem is complicated, so that's to be expected. I meant all that copying there and back and having two variables with the same name in two scopes and handling all combinations when to access which one. I wanted to take this away. What is bad with scenario of reusing the same variable and isolating it as local only for the async closures? Your proposal depends on being able to reassign variable pointers, but they don't necessarily exist. Though I haven't written a JS engine, I believe they are allowed to have variables directly in an activation record (or environment instance) without any (pointer) indirection, so they'd have no mechanism for performing the operation you describe. Well, references are all over the spec (or were in times of ES5). Yes, they are only virtual objects of the spec, they are not first-class and probably not even exist in the implementation; but I just reused them in my proposal. So that there will be loop-body-local variable _iter_record; 'i' inside body will be Ref(_iter_record, 'i'), and while the loop is running, _iter_record is _outer_shared_record, and when ending the iteration, _iter_record is assigned to _local_iter_record. Of course if there are no closures, all this can be optimized away, or if eval is not used in them, only those variables that needs to be localized should be; but that's beyond scope. (No, I am not proposing optimization, as Brendan said; I am proposing different approach with lazy fixing locals so that complications are not present while the loop is running, only when closures are run outside loop) Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. If I understood correctly, this is what I proposed. Or maybe it only looks like it? In each case, you require a list of not-necessarily-predictable size to note the closures in. That's not a big problem; it's just something you need to be aware of. This I did not understand. Regards, Grant. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Herby Vojčík wrote: I meant all that copying there and back and having two variables with the same name in two scopes and handling all combinations when to access which one. I wanted to take this away. Yes, at least my desugaring was indeed overcomplicated. Brendan's and Mark's desugarings aren't that complicated, though. Your proposal depends on being able to reassign variable pointers, but they don't necessarily exist. Well, references are all over the spec (or were in times of ES5). What I was calling activation records, the spec calls environment records and it does not, by my reading, imply that variables within them are reference-like in nature (read clause 10.2). However, we don't need to argue this point. Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. If I understood correctly, this is what I proposed. Or maybe it only looks like it? It has the same behaviour, but without needing variable pointers; that was the idea. I now propose it to the list as a variant of your idea that I think some may prefer. I'm merely trying to make sure your idea gets the attention it deserves. To be completely acceptable, the mechanics would need fleshing out, of course. Regards, Grant. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Grant Husbands wrote: Herby Vojčík wrote: Your proposal depends on being able to reassign variable pointers, but they don't necessarily exist. Well, references are all over the spec (or were in times of ES5). What I was calling activation records, the spec calls environment records and it does not, by my reading, imply that variables within them are reference-like in nature (read clause 10.2). However, we don't need to argue this point. I feel like misunderstood. I _know_ there aren't ref variables in the spec. I never told that. I was just telling that 'i' inside the loop body may be compiled not as Ref(loop-body-record, 'i') whenever it is accessed/modified (and optimized when Ref is not really needed, but you can see at it as if it is always Ref to local i), but as Ref(the-record-to-use, 'i'), the-record-to-use being local variable in loop iteration which can change its value (it can be outer-record when loop runs, at it can be local-record when iteration ends and values are fixed in local 'i'). Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. If I understood correctly, this is what I proposed. Or maybe it only looks like it? It has the same behaviour, but without needing variable pointers; that was the idea. I now propose it to the list as a variant of your idea that I think some may prefer. I'm merely trying to make sure your idea gets the attention it deserves. Thanks. Yes it is the same behaviour, the active loop body always uses the same shared 'i', only async closures get the fixed copy. Nice. To be completely acceptable, the mechanics would need fleshing out, of course. Regards, Grant. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Grant Husbands wrote: For what you're talking about, I think this might be an equivalent proposal that's more spec-friendly: 'Note' all closures (dynamically) created in (lexically) the loop initializer. Only in the initializer? Why should closures formed there be dynamically scoped to the current iteration? That's something done nowhere in the language as extended by let/const, either in JS1.7+ in SpiderMonkey and Rhino, or in draft ES6 so far. I admit it's yet another non-throwing possibility beyond 0th-iteration scope and 1st-iteration scope. It is not the same as evaluating the initializer in each iteration's scope, though. Instead there's a dynamic scope for closures in the initializer (only those using the for(let-declared bindings?) not used for expressions or other closures, or something like that. Dynamic scope is a warning sign, almost always a mistake. Each time you start an iteration, update all the loop variable activation record pointers within those to point at the current iteration's activation record (which should, with care, have the same shape). Note that non-strict eval can affect the activation's shape. Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. This is a more complex spec than one that models each iteration having its own lexical scope. The spec needs only declarative environments, not hidden references and pointer updates. In each case, you require a list of not-necessarily-predictable size to note the closures in. That's not a big problem; it's just something you need to be aware of. As an implementation technique, Chez Scheme's heap boxing and assignment conversion could be even better. But this is all beyond the spec. Still trying to be sure you intended a unique and dynamic scope for the initializer (first part) of for(let;;). /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Fri, Feb 3, 2012 at 7:26 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 3, 2012, at 4:26 PM, Jason Orendorff wrote: On 2/3/12 6:13 PM, Allen Wirfs-Brock wrote: But I also have to validly (and hopefully reasonably) specify exactly what happens for the unrealistic use cases. There is a problem with your desugaring in that the evaluation of INIT isn't scoped correctly relative to V. Hmmm. I don't see the problem yet. I think it's scoped the way I intended it: INIT is evaluated in the enclosing environment; V isn't in scope. Under the scoping rules TC39 has agreed to, the initializer of a let/const is always shadowed by the binding it is initializing[...] That rule doesn't make sense in this context. There should be either one V for the whole loop, or one V per iteration. Having both seems perverse. Again, one can focus on parallels between for(let V=E;;) and let V=E; or on parallels between for(let V=E;;) and for (let V of E). I find the latter more important because experience and evidence suggest that that is what will affect users in practice. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Fri, Feb 3, 2012 at 6:13 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 3, 2012, at 12:44 PM, Jason Orendorff wrote: It might also be useful to look at for(var ...;...;...) and for (...;...;...) loops. Nobody is proposing changing the scoping for those, but they might provide a broader view of how people use the generality of for(;;) loops Out of about 5,500 for loops, I found none with functions in the head. Actually, though, Mozilla's codebase is no better than anyone else's for that purpose. I only picked ours because we have 'let'. I wish we had a bigger corpus to search. This is excluding tests generated by Mozilla's JS fuzzers; the fuzzers often says things like: for(e in this.__defineSetter__(x,function(){})){} ...and much worse. I would actually feel a lot more comfortable with per iteration binding of for(;;) if the for let declaration was limited to a single binding. Of course, that would inconvenience people doing simple stuff that has nothing to do with closures, as in: for (let i = 0, n = a.length; i n; i++) for (let child = element.firstChild, stop = element.lastChild; child !== stop; child = child.nextSibling) My sense is that these uses are even more common than for-loops with escaping closures. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Sat, Feb 4, 2012 at 8:02 AM, Jason Orendorff jason.orendo...@gmail.com wrote: On Fri, Feb 3, 2012 at 7:26 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Under the scoping rules TC39 has agreed to, the initializer of a let/const is always shadowed by the binding it is initializing[...] That rule doesn't make sense in this context. There should be either one V for the whole loop, or one V per iteration. Having both seems perverse. I just realized—the loop variables have to be visible in the init-expressions if we want to support this: for (let a = getThings(), i = 0, n = a.length; i n; i++) This is maybe not the best way to write for (thing of getThings()), but people will write it, and so it probably ought to work. I think this is more important than escaping closures. This means that if such loops will have per-iteration bindings, they should have an additional set of bindings just for initialization-time—which seems ugly. Maybe it's not worth it. There is also this: for (let i = 0; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) i++; } This will not work no matter what semantics we choose. However, per-iteration bindings risk encouraging people to hit this problem. These two issues give me pause. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 4, 2012, at 6:02 AM, Jason Orendorff wrote: On Fri, Feb 3, 2012 at 7:26 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 3, 2012, at 4:26 PM, Jason Orendorff wrote: On 2/3/12 6:13 PM, Allen Wirfs-Brock wrote: But I also have to validly (and hopefully reasonably) specify exactly what happens for the unrealistic use cases. There is a problem with your desugaring in that the evaluation of INIT isn't scoped correctly relative to V. Hmmm. I don't see the problem yet. I think it's scoped the way I intended it: INIT is evaluated in the enclosing environment; V isn't in scope. Under the scoping rules TC39 has agreed to, the initializer of a let/const is always shadowed by the binding it is initializing[...] That rule doesn't make sense in this context. There should be either one V for the whole loop, or one V per iteration. Having both seems perverse. I agree, but having both is exactly what for(let;;) requires in order to satisfy (actually approach satisfying) everybody. If you want the simplicity of the either or alternative then a good approach seems to be that for-in/for-of is one per iteration and for(;;) is one for the whole loop. C# recently when though a similar design change and that is exactly where they ended up. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 4, 2012, at 8:01 AM, Jason Orendorff wrote: On Sat, Feb 4, 2012 at 8:02 AM, Jason Orendorff jason.orendo...@gmail.com wrote: On Fri, Feb 3, 2012 at 7:26 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Under the scoping rules TC39 has agreed to, the initializer of a let/const is always shadowed by the binding it is initializing[...] That rule doesn't make sense in this context. There should be either one V for the whole loop, or one V per iteration. Having both seems perverse. I just realized—the loop variables have to be visible in the init-expressions if we want to support this: for (let a = getThings(), i = 0, n = a.length; i n; i++) This is maybe not the best way to write for (thing of getThings()), but people will write it, and so it probably ought to work. I think this is more important than escaping closures. This means that if such loops will have per-iteration bindings, they should have an additional set of bindings just for initialization-time—which seems ugly. Maybe it's not worth it. That's what the initial let V = INIT I said you needed in your de-sugaring was intended to address There is also this: for (let i = 0; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) i++; } This will not work no matter what semantics we choose. However, per-iteration bindings risk encouraging people to hit this problem. The } finally { %tmp = V } in your desugaring takes care of making sure that the the V value of the previous iteration is available to the TEST expression These two issues give me pause. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
I want off this merry-go-round! Let's recap: From the January 19th 2012 notes Waldemar took: Discussion about scope of for-bindings. for (var x = ...;;) {...} will, of course, retain ES1 semantics. for (let x = ...;;) {...} Allen: This will behave as in C++: x is bound once in a new scope immediately surrounding just the for statement. DaveH: Strangely enough, this creates a new x binding in Dart at each iteration. There's an alternative semantics that creates an iteration-local second x inside the loop and copies it back and forth. Debate about whether to go to such complexity. Many of us are on the fence. Waldemar: What happens in the forwarding semantics if you capture the x inside a lambda in any of the three expressions in the head? If this happens in the initializer: DaveH's option: The lambda would capture an outer x. Alternative: The lambda captures a hidden second x. Waldemar's option: The lambda would capture the x from the first iteration. The let variable x is bound once through each iteration, just before the test, if for (let x = expr1; expr2;) {...} were: while (true) { let x = first_iteration ? expr1 : value_of_x_from_previous_iteration; if (!expr2) break; ... } MarkM: Just discovered that his desugaring has the same semantics as Waldemar's option. --- end waldemar notes --- Mark's desugaring (https://mail.mozilla.org/pipermail/es-discuss/2008-October/007819.html): You're right. However, the desugaring is more complex than I expected. Thanks for asking me to write it down. for (keyword varName =initExpr;testExpr;updateExpr) {body } desugars to (hygienic renaming aside): breakTarget: { const loop = lambda(iter =initExpr) { keyword varName = iter; if (!testExpr) { break breakTarget; } continueTarget: {body } lambda(iter2 =varName) { keyword varName = iter2; updateExpr; loop(varName); }(); }; loop(); } I believe this meets all your requirements. However, in contradiction to my original claim, one couldn't usefully say const instead of let with a for(;;) loop. --- end mark desugaring --- But then Jon Zeppieri wrote in reply (https://mail.mozilla.org/pipermail/es-discuss/2008-October/007826.html): / I believe this meets all your requirements. / I believe it does. Very cool. It won't handle the fully general for(;;). E.g., for (let fn = lambda(n) { ... fn(...) ... };testExpr;updateExpr) ... Also, for (let i = 0, j = i + 1; ...) ... But the modifications needed to make these work are pretty straightforward. --- end jon citation --- Then Grant Husbands wrote (https://mail.mozilla.org/pipermail/es-discuss/2012-January/019804.html): How about something like this? (given for (letvarName =initExpr;testExpr;updateExpr) {body } ) { letvarName =initExpr; while(true) { if (!testExpr) { break breakTarget; } lettempVar =varName; { // There might be a better way to copy values to/from shadowed variables // (using temporaries seems a bit weak) letvarName =tempVar; continueTarget: {body } tempVar =varName; } varName =tempVar; updateExpr; } } ... Maybe disallowing capture in the for (let ...;...;...) head would be easier. --- end grant citation --- Ok, Brendan here. I agree we want to support multiple declarators for the let or const in the for-head's initialization part. I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. This requires unrolling the loop once. Let's see how the desugaring from: for (let d1 = e1, ... dN = eN; cond; update) { body; } looks. It doesn't seem terrible: $loopEnd: { let d1 = e1, ... dN = eN; if (cond) { body; update; const $loop = { |d1, ... dN| if (!cond) break $loopEnd; body; update; $loop(d1, ... dN); } $loop(d1, ... dN); } } Notes: * ... is meta-syntax, not rest/spread syntax. * I've left out break and continue in the body. * I'm using a block lambda for fun. Mutations to the first-iteration d1, ... dN bindings in any closures in e1...N propagate to the second iteration. Is this enough to restore the consensus we thought we had at the end of the meeting day on January 19th? /be Jason Orendorff mailto:jason.orendo...@gmail.com February 4, 2012 8:01 AM On Sat, Feb 4, 2012 at 8:02 AM, Jason Orendorff I just realized—the loop variables have to be visible in the init-expressions if we want to support this: for (let a = getThings(), i = 0, n = a.length; i n; i++) This is maybe not the best way to write for (thing of getThings()), but people will write it, and so it probably ought to work. I think this is more important than escaping closures. This means that if such loops will have per-iteration bindings, they should have an additional set of bindings just for initialization-time—which seems ugly. Maybe it's not worth it. There
Re: lexical for-in/for-of loose end
On Feb 4, 2012, at 9:49 AM, Brendan Eich wrote: I want off this merry-go-round! Let's recap: thanks for the recap... ... From the January 19th 2012 notes Waldemar took: Discussion about scope of for-bindings. for (var x = ...;;) {...} will, of course, retain ES1 semantics. for (let x = ...;;) {...} Allen: This will behave as in C++: x is bound once in a new scope immediately surrounding just the for statement. DaveH: Strangely enough, this creates a new x binding in Dart at each iteration. There's an alternative semantics that creates an iteration-local second x inside the loop and copies it back and forth. Debate about whether to go to such complexity. Many of us are on the fence. Waldemar: What happens in the forwarding semantics if you capture the x inside a lambda in any of the three expressions in the head? If this happens in the initializer: DaveH's option: The lambda would capture an outer x. Alternative: The lambda captures a hidden second x. Waldemar's option: The lambda would capture the x from the first iteration. The let variable x is bound once through each iteration, just before the test, if ... --- end grant citation --- Ok, Brendan here. I agree we want to support multiple declarators for the let or const in the for-head's initialization part. check I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. It isn't clear to me why capture first-iteration is abstractly any better than capture a hidden second x. In both cases, in most iterations of the loop, evaluation of any such captures is going to reference the wrong binding. From a user perspective, the main advantage I see for capture first iteration is that it has a slightly smaller window of wrongness. The captures evaluated in the first iteration will reference the correction binding, while latter iterations reference the wrong binding. From an implementation perspective, it is probably a bit simpler to not have the extra hidden binding for capture. There is another alternative that I haven't seen mentioned. Scope any closures in the initialization expression such that any references to loop declared binding resolve to undeclared bindings, For example (using block lambdas for conciseness) for( let d1={|| d1}, d2={|| d1+d2};;)... would actually be interpreted as if it was for (let d1={|| return {|| d1}; let d1,d2}(), let d2={|| return {|| d1+d2}; let d1,d2}();;)... In words, any references to loop declarations capture uninitialized bindings that never get initialized. If evaluated they will throw as accesses within the TDZ of the binding. Always making such bindings (when used) a error seems preferable I(from a user perspective) than making the first iteration of a loop behave differently from subsequent iterations. ... Mutations to the first-iteration d1, ... dN bindings in any closures in e1...N propagate to the second iteration. Is this enough to restore the consensus we thought we had at the end of the meeting day on January 19th? I really don't like the first iteration is different semantics and think we should think about the above alternative. However, such closure capture is very rare (could use of block lambda based patterns change that??) so it may come down to judgements about implementation costs. Is capture first going to be significantly easier to implement than my alternative scoping? The answer is obvious to me. In either case an implementation is like to special case loops with closure capture in their initializers. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich wrote: $loopEnd: { let d1 = e1, ... dN = eN; if (cond) { body; update; const $loop = { |d1, ... dN| if (!cond) break $loopEnd; body; update; $loop(d1, ... dN); } $loop(d1, ... dN); } } Notes: * ... is meta-syntax, not rest/spread syntax. * I've left out break and continue in the body. * I'm using a block lambda for fun. Mutations to the first-iteration d1, ... dN bindings in any closures in e1...N propagate to the second iteration. Another note: * this is a desugaring, so any of d1...N can be destructuring patterns, and with the structuring shorthand for object literals the bindings propagate nicely. Symmetry FTW. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 4, 2012, at 9:49 AM, Brendan Eich wrote: I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. It isn't clear to me why capture first-iteration is abstractly any better than capture a hidden second x. In both cases, in most iterations of the loop, evaluation of any such captures is going to reference the wrong binding. The argument is as follows: for (let i = 0, a = some.array, n = a.length; i n; i++) { ... } here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. Now consider a bit of eta conversion: for (let i = 0, a = some.array, n = (function(){return a})().length; i n; i++) { ... } It would be quite wrong for the a captured by the anonymous function expression to be other than the a binding declared and initialized immediately to the left. It would be bad for the eta conversion to break equivalence (use a block-lambda instead of a function expression for full TCP). From a user perspective, the main advantage I see for capture first iteration is that it has a slightly smaller window of wrongness. The captures evaluated in the first iteration will reference the correction binding, while latter iterations reference the wrong binding. Users expect and even (now that they know, and Dart raises the ante) demand that each iteration gets fresh let bindings. Any who do capture an initial binding in a closure must know, or will learn, that it's just the first one, which fits the model. If this were really a footgun (I don't believe it is without actual evidence from the field) we could try to ban closures capturing the initial bindings. That ad-hoc restriction would be quite a wart. It doesn't seem warranted. From an implementation perspective, it is probably a bit simpler to not have the extra hidden binding for capture. I don't think so. The unrolling I showed was to use a tail-recursive block-lambda helper. But real implementations will do closure analysis and optimization (flat AKA display closures, e.g.) and use branch instructions for loops, jumps for breaks, etc. Having the first binding rib open a bit earlier than subsequent ribs is (I think) a small or zero-cost issue. I really don't like the first iteration is different semantics Different how? Making the first iteration's binding initialization capture guaranteed errors would be different semantics. Capturing the first iteration's bindings from closures in their initializers is not different any more than having initializers is different. The initialization part of the for loop is already special. It's not like the update part. and think we should think about the above alternative. Eta equivalence matters. Given that we want n = a.length to use the a declared to the left in the same for-head declaration, I don't see how we can make closures in a right-ward initializer capture some outer binding. Capturing an error-only binding would need evidence of the footgun not being useful for shooting other things. We don't have such evidence, not by a long shot. However, such closure capture is very rare (could use of block lambda based patterns change that??) I don't think so -- equivalences are stronger, not weaker or different, with block-lambdas vs. functions, due to TCP. so it may come down to judgements about implementation costs. Is capture first going to be significantly easier to implement than my alternative scoping? The answer is obvious to me. Did you mean isn't? In either case an implementation is like to special case loops with closure capture in their initializers. Varying a sketch Jason posted to the SpiderMonkey internals list: for (let V = INIT; TEST; UPD) STMT compiles to enterblock V INIT goto L2 L1: STMT reenterblock UPD L2: TEST iftrue L1 leaveblock This is very close to how SpiderMonkey compiles for(let;;) already -- the only new instruction is reenterblock, which exits the current block and does the equivalent of enterblock V again. V is an immediate operand, in SpiderMonkey a block object literal created by the compiler. It holds all the bindings and their stack offsets, however many declarators with or without destructuring occur after let. I claim implementation is not the driver here. User expectations, esp. savvy users who might make some practical or theoretical (testing) use of eta conversion, matter more. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
It looks to me that all those schemes and desugaring are very complicated. What is bad with scenario of reusing the same variable and isolating it as local only for the async closures? The code: for (let a=..., b=..., otherInit; cond; update) { // use a, b and things from otherInit } Now, if you allow first class refs _in_implementation_, it could be something like this: let a=..., b=..., otherInit; while (cond) { { let _inner_a, _inner_b; // undefined as yet; let ref a :- ref outer a, ref b :- ref outer b, ...; // (1) try { // use a, b and things from otherInit // names a, b and others from for-let are compiled using // refs defined in (1) which point to outer // variables while processing loop body } finally { _inner_a = a; _inner_b = b; ... // assign actual values ref a :- ref _inner_a; ref b :- ref _inner_b; ... // fixing refs at the end of the body to local ones // async closures will use these (since they use the refs // which were rerouted) // the same closures when executed inside loop body would // of course use the outer shared variables, since fixing // only happens after the body // It is always the same ref, but points to different things // in loop and out of the loop } } update; } This way, inside the loop, always the same (outer) variables are used, any inc() things will act upon them, no copying in-out. At the end of every iteration, the refs that pointed to these shared variables are fixed to inner ones, using actual values. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 4, 2012, at 12:55 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: On Feb 4, 2012, at 9:49 AM, Brendan Eich wrote: I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. It isn't clear to me why capture first-iteration is abstractly any better than capture a hidden second x. In both cases, in most iterations of the loop, evaluation of any such captures is going to reference the wrong binding. The argument is as follows: for (let i = 0, a = some.array, n = a.length; i n; i++) { ... } here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. Now consider a bit of eta conversion: for (let i = 0, a = some.array, n = (function(){return a})().length; i n; i++) { ... } It would be quite wrong for the a captured by the anonymous function expression to be other than the a binding declared and initialized immediately to the left. Yes, I support the general principal that that initializers of bindings capture to the left. But the problem here is that conceptually the let is defining multiple bindings (one per iteration). I don't think many people are actually going to understanding the details of the proposed semantics and their implications. Since most uses won't involve closure capture, any of the proposed semantics that have per iteration bindings with forward value propagation are just going to do the right thing. That is good. However, I doubt that someone who actually codes a function in a for(let;;) initializer is going to be thinking, of course, this only captures the first iteration bindings. It would be bad for the eta conversion to break equivalence (use a block-lambda instead of a function expression for full TCP). With the TDZ alternative I proposed, there would still be equivalence for: for(let x=x;;)...; and for(let x={|| x}();;)...; Both throw for accessing an initialized variable. But you're right that equivalence is lost for for(let x=n, y=x;;)...; and for(let x=n, y={|| x}();;)...; Whether this is better or worse than the wrong capture issue complete depends upon the actual programmer intent. From a user perspective, the main advantage I see for capture first iteration is that it has a slightly smaller window of wrongness. The captures evaluated in the first iteration will reference the correction binding, while latter iterations reference the wrong binding. Users expect and even (now that they know, and Dart raises the ante) demand that each iteration gets fresh let bindings. Any who do capture an initial binding in a closure must know, or will learn, that it's just the first one, which fits the model. For the latter, I strongly suspect that they won't know and will be WTF surprised when they encounter it. The saving grace is that this will probably be very rare, although its possible that the introduction of block lambdas might somewhat change that. Just don't know... If this were really a footgun (I don't believe it is without actual evidence from the field) we could try to ban closures capturing the initial bindings. That ad-hoc restriction would be quite a wart. It doesn't seem warranted. My TDZ solution is such a restriction. But I don't see how it is any more ad hoc than any of the other changes we are talking about here in order to give for(;;) per iteration bindings. Its wartiness actually seems small and is restricted to a situation where the programmer probably is actually expecting C=style per loop rather than per iteration binding. From an implementation perspective, it is probably a bit simpler to not have the extra hidden binding for capture. I don't think so. The unrolling I showed was to use a tail-recursive block-lambda helper. But real implementations will do closure analysis and optimization (flat AKA display closures, e.g.) and use branch instructions for loops, jumps for breaks, etc. Having the first binding rib open a bit earlier than subsequent ribs is (I think) a small or zero-cost issue. My comment wasn't about the bind to first unrolling. It was about the extra hidden binding alternative in the first list of alternatives and is probably also applicable to by TDZ alternative. I really don't like the first iteration is different semantics Different how? Different from subsequent iterations... Take Jason's example for (let i = 0; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) i++; } If somebody decided to abstract the increment: for (let i = 0; advance={|| i++}; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) advance(); } advance does what is intended if called on the first iteration, but not on subsequent one iterations. I'd soon get an error when I ran this than having
Re: lexical for-in/for-of loose end
Herby Vojčík wrote: It looks to me that all those schemes and desugaring are very complicated. And what you showed isn't complicated? LOL. What you're sketching is an optimization, one I believe Chez Scheme and other implementations perform. Indeed any good implementation will optimize let-bound loop variables to registers and avoid any per-iteration overhead in the absence of closures. But what you sketched is nothing like a specification. A normative specification does not describe non-observables, except in informative asides (which should be used sparingly). A specification must simply and clearly state the rules for syntax and semantics. In particular, the ES6 specification governing the for-let loops we're discussing needs specific treatment to avoid the notorious var pigeon-hole problem made observable by closures in the loop body that capture loop variables. The simplest way to do this is being worked out, but it will necessarily entail a scope per iteration. I argue the first iteration's scope must enclose the initialisers for the for(let;;) head's first of three parts. This is an important detail. It's not complicated beyond other parts of the spec; it's just detailed. We have to have consistent for(let;;) semantics, and as the discussion has evolved (since 2008) we've come to agree on the constraints by which we reckon consistent. The constraints require a specific solution, in my view: first iteration's scope covers iniitalizers, and observably-distinct let scopes for each iteration. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 4, 2012, at 12:55 PM, Brendan Eich wrote: The argument is as follows: for (let i = 0, a = some.array, n = a.length; i n; i++) { ... } here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. Now consider a bit of eta conversion: for (let i = 0, a = some.array, n = (function(){return a})().length; i n; i++) { ... } It would be quite wrong for the a captured by the anonymous function expression to be other than the a binding declared and initialized immediately to the left. Yes, I support the general principal that that initializers of bindings capture to the left. But the problem here is that conceptually the let is defining multiple bindings (one per iteration). I don't think many people are actually going to understanding the details of the proposed semantics and their implications. Since most uses won't involve closure capture, any of the proposed semantics that have per iteration bindings with forward value propagation are just going to do the right thing. That is good. That's all agreed, yes. However, I doubt that someone who actually codes a function in a for(let;;) initializer is going to be thinking, of course, this only captures the first iteration bindings. Why? The initializer runs first. There are two working possibilities: bindings of the same name in an implicit block around the loop (the 0th iteration scope), immediately shadowed on first (if the condition is true) by fresh bindings of the same names with values forwarded; or what I propose, the first iteration's bindings. Why is the 0th iteration scope better than the 1st iteration scope? It may be that some people think of the INIT part in for (INIT; TEST; UPD) STMT as being before the loop starts, but others may see it as part of the first iteration. If we really do have evidence for the 0th iteration scope (hard to imagine), then that is doable but it costs a bit more in the closure-in-initializer case. All else equal I go with lower cost in that rare case. With the TDZ alternative I proposed, there would still be equivalence for: for(let x=x;;)...; and for(let x={|| x}();;)...; Both throw for accessing an initialized variable. Yippee. :-| Making a dynamic error trap for no good reason is a mistake. Where's the evidence that closures in initializers that capture loop control variables are wrong? There isn't any, AFAIK. But you're right that equivalence is lost for for(let x=n, y=x;;)...; and for(let x=n, y={|| x}();;)...; Whether this is better or worse than the wrong capture issue complete depends upon the actual programmer intent. It's worse for the language's consistency of binding rules and equivalences. It's a botch. I think we should not equivocate here. Users expect and even (now that they know, and Dart raises the ante) demand that each iteration gets fresh let bindings. Any who do capture an initial binding in a closure must know, or will learn, that it's just the first one, which fits the model. For the latter, I strongly suspect that they won't know and will be WTF surprised when they encounter it. Why? What binding did the think they captured, given the assumption we share (since you didn't reject it, we do share this, right?) that they know about fresh binding(s) per iteration? Are you implying users will expect the 0th-iteration bindings? Could be, but I rather suspect they'd want 1st. There isn't another choice. They don't want throwing upvars (throwing-up vars :-P). Different how? Different from subsequent iterations... Take Jason's example for (let i = 0; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) i++; } If somebody decided to abstract the increment: for (let i = 0, advance={|| i++}; i n; ) { setTimeout(...closure using i...); if (shouldAdvance()) advance(); } advance does what is intended if called on the first iteration, but not on subsequent one iterations. I'd soon get an error when I ran this than having it get stuck in a loop. (I s/;/,/ on the for line to fix the typo.) Anyone who writes that must be thinking the advance block-lambda is in the scope of the body, if they understand fresh-binding-per-iteration. But the advance block-lambda is not in the body. It's not before the loop either (wrong [outer] or no i in scope there). This is simply erroneous code by definition -- the assumption of fresh-binding-per-iteration having been learned already is violated by the structure of the code. Rather than a dynamic error (might not test this), anyone writing this might rather get a static error. A warning would be justifed by a high quality implementation, based on analysis of the advance() call possibly coming from later iterations. So here we return to
Re: lexical for-in/for-of loose end
Brendan Eich wrote: I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers. This requires unrolling the loop once. Let's see how the desugaring from: [snip] For what it's worth, I agree with Mark's (2008) and Brendan's desugarings and hereby withdraw my own, as the value-copying covers barely any more use cases. Without some extensive feature like let-aliasing, as Herby suggested, those solutions as good as we're going to get, though I could have missed some of the discussion so far. By the way, if apparent code duplication in Brendan's desugaring is an issue, a small change will fix that, with little cost, like so: $loopEnd: { let $initdone = false; const $loop = { |d1, ... dN| if (!$initdone) { $initdone = true; d1 = e1, ... dN = eN; } if (!cond) break $loopEnd; body; update; $loop(d1, ... dN); } $loop() } As for modifications done by the for-head's closures, I have no strong opinions. In both proposed desugarings, those modifications would be invisible to the loop body fairly quickly. And in all proposals that don't involve some kind of extensive variable aliasing, the for-head's closures cannot observe values set in the loop body (beyond the first iteration). However, I hope I don't delay consensus by raising the possibility of another desugaring using consts: $loopEnd: { const d1 = e1, ... dN = eN; const $loop = { |d1, ... dN| if (!cond) break $loopEnd; body; update; $loop(d1, ... dN); } $loop(d1, ... dN); } That way, the for-head's closures can't easily have hidden unexpected effects, though they'll still observe incorrect iterated values, and it's less of a sledgehammer than disallowing capture altogether. It's late, so I apologise if I've missed something really obvious or typed garbage. Regards, Grant. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Herby Vojčík wrote: It looks to me that all those schemes and desugaring are very complicated The full problem is complicated, so that's to be expected. What is bad with scenario of reusing the same variable and isolating it as local only for the async closures? Your proposal depends on being able to reassign variable pointers, but they don't necessarily exist. Though I haven't written a JS engine, I believe they are allowed to have variables directly in an activation record (or environment instance) without any (pointer) indirection, so they'd have no mechanism for performing the operation you describe. However, though I haven't read it, I believe that the spec talks a great deal about these activation records and environments, so specifying a mechanism involving those might give you more chance of finding common ground. For what you're talking about, I think this might be an equivalent proposal that's more spec-friendly: 'Note' all closures (dynamically) created in (lexically) the loop initializer. Each time you start an iteration, update all the loop variable activation record pointers within those to point at the current iteration's activation record (which should, with care, have the same shape). Or, here's one that copies the other way (and is probably cleaner): 'Note' all closures (dynamically) created in (lexically, post-desugaring) the loop body. Each time you end an iteration, update all the loop variable activation record pointers to point at a new clone of that activation record. In each case, you require a list of not-necessarily-predictable size to note the closures in. That's not a big problem; it's just something you need to be aware of. Regards, Grant. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On 3 February 2012 03:08, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Plus, the desugarings aren't things that are really suitable for teaching the semantics to everyday JS programmers. You instead have to say something like: Ok, this is really complicated but here goes. For each let/const declared in the for header, a fresh variable is located in the loop body for each iteration of the loop. However, the values of the loop variables are automatically copied from the previous iteration into the next iterations. This means that basic expression operator will work in the loop header pretty much like you would expect. But be careful if you use any function expressions in the for header because the loop variables they reference may not be from the current iteration and any changes to loop variable they make may not have the effect you intended. But, hey you probably shouldn't do those things so it really doesn't matter what it really does. I don't understand your bit saying that values are copied from the previous iteration into the next iterations. In any case, it seems to me that saying the following is perfectly enough: Declarations in the loop header work like normal declarations. However, inside the loop body, for each iteration, you get a fresh copy of all variables bound in the header, with their current values. (This does not affect uses of the variables in the loop header itself.) The last sentence is just for clarity, as it is already implied by the other two. Moreover, it shouldn't matter for any sane kind of program. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 2, 2012, at 1:38 PM, Brendan Eich wrote: keys etc. are currently spec'ed to to do own-only, you want allKeys to do the crazy thing: http://wiki.ecmascript.org/doku.php?id=harmony:iterators#standard_api Let's not go around and around on this. Enumerable proto-properties are harmful, no one wants them. I'm afraid I have to disagree with the absolutism of this statement. There are some perfectly reasonable use cases for enumerating proto properties and they are quite in alignment with prototypal inheritance. For example, how about defining a set of default property values and then using prototypal inheritance to selective over-ride them: let privateImplicitOptions = {a:1,b:2,c:3 /* and on and on */}; function makeOptions(explicitOptions) { let opt = Object.create(privateImplicitOptions); for (let [k.,v] of items(explicitOptions)) opt[k]=v; return opt; } myOpts = makeOptions({b:-2}); Why should somebody I pass an options object to need to worry about whether I used zero, one, or n levels of prototypes to represent by options. +1 that said, I think we agree that the real problem is the legacy ES semantics of for-in and its use (or lack of) for the enumerable attribute. If we can start out with a clean slate conceptual slate for for-of maybe we don't have to worry about such things. However, carrying over a lot of historic baggage isn't a clean slate. Thinking in terms of abstract collections, having keys() not mean all keys in the collection seems quite absurd. That should be the normal case, the more verbose name should be used for the exceptional case that excludes some of the keys. Now in this case, the real problem is in deciding the default way to view a plain Es objects as an abstract collection. Maybe only considering own properties as the collection elements is the right thing. In that case it may be allKeys that is carrying the wrong implication and instead it should be something like extendedKeys. deepKeys, maybe? Allen Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: for-in works well with a per iteration binding because there is no header code that runs between iterations. The various desugarings that have been proposed to provide a fresh copy for each iteration of this form of for work for propagating simple values between the header and each iteration but not for closure captures. Plus, the desugarings aren't things that are really suitable for teaching the semantics to everyday JS programmers. You instead have to say something like: Ok, this is really complicated but here goes. For each let/const declared in the for header, a fresh variable is located in the loop body for each iteration of the loop. However, the values of the loop variables are automatically copied from the previous iteration into the next iterations. This means that basic expression operator will work in the loop header pretty much like you would expect. But be careful if you use any function expressions in the for header because the loop variables they reference may not be from the current iteration and any changes to loop variable they make may not have the effect you intended. But, hey you probably shouldn't do those things so it really doesn't matter what it really does. Ok, alternative approach: for (let i=..., whatever; whatever2; whatever3) { // use i } be implemented as let i=..., whatever; while (whatever2) { { let i=forward_proxy_to(outer i); // a thing that touches real i, but is different object // use i i=current value of inner i; // fix inner i to the value // The point is, it works for closures, both from inside or // 'detached' since it is always the same 'i' binding // in the same execution context } whatever3; } Allen Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Thu, Feb 2, 2012 at 8:08 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 2, 2012, at 5:07 PM, Jason Orendorff wrote: On Thu, Feb 2, 2012 at 5:52 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: for (let i=(geti = function() {return i},expr), expr, incr = function(i++), decr=function(i--), cmp=function(){return in}; cmp();incr()) { What?! We should not reward people writing code like that. No reward, it is simply what the current syntax permits, and it is important that semantics are consistently applied. Oh, I definitely think we should have consistent semantics. It's difficult, because for(;;) is complicated, but it's worth the effort. Consider: for (let V in EXPR) STMT for (let V of EXPR) STMT for (let V = EXPR; ...; ...) STMT I propose that in all three cases, the code that runs once, before the first iteration, namely EXPR, should be evaluated outside the scope of the loop variable. STMT should be evaluated within the scope of a per-iteration binding for V. The same semantics in all three cases. That seems to me both consistent and sensible. What is even more important than consistency is that the language work for people. We know that *not* having per-iteration bindings astonishes users, because that's how SpiderMonkey does it, and people are regularly astonished. Plus, the desugarings aren't things that are really suitable for teaching the semantics to everyday JS programmers. You instead have to say something like: Ok, this is really complicated but here goes. For each let/const declared in the for header, a fresh variable is located in the loop body for each iteration of the loop. However, the values of the loop variables are automatically copied from the previous iteration into the next iterations. This means that basic expression operator will work in the loop header pretty much like you would expect. But be careful if you use any function expressions in the for header because the loop variables they reference may not be from the current iteration and any changes to loop variable they make may not have the effect you intended. But, hey you probably shouldn't do those things so it really doesn't matter what it really does. This explanation seems oriented more towards making a rhetorical point than explaining. If I had to explain this to an everyday Web programmer, I would just write the three lines of code above and point out the consistent behavior. That way is simple, and it explains several things at once. Anyway, this kind of argument certainly isn't going to win me over, because the reason I want this change in the first place is so I can do less explaining! People do write code like this: for (let i = 0; i n; i++) { buttons[i] = makeButton(); buttons[i].click(function () { alert(pushed button + i); }); } It fails to work because in SpiderMonkey the binding isn't per-iteration. Then I have to explain. Per-iteration bindings mean less explaining, because fewer people will hit problems. Their code will just work. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
My attempt at a for-let desugaring is: https://gist.github.com/1730064 I think this will be very easy to implement, though I haven't tried it yet: http://groups.google.com/group/mozilla.dev.tech.js-engine.internals/browse_thread/thread/4906c7389e18f0f8 -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 3, 2012, at 8:46 AM, Jason Orendorff wrote: My attempt at a for-let desugaring is: https://gist.github.com/1730064 yes, similar to https://mail.mozilla.org/pipermail/es-discuss/2008-October/007819.html the real issue with per iteration binding and for(;;) is closure capture in the (;;). This is admirably rare, but your definition (and Mark's) means that when it occurs it is unlikely to do what the programmer intended. Allen___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Fri, Feb 3, 2012 at 11:23 AM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 3, 2012, at 6:42 AM, Jason Orendorff wrote: for (let V in EXPR) STMT for (let V of EXPR) STMT for (let V = EXPR; ...; ...) STMT the third case is different in several ways. Including: let V =EXPR is a distinct syntactic pattern that has a specific semantics that requires that the EXPR is evaluated in the same scope as EXPR. However, in most cases (but consider |et x=(x=5,++x);) all of EXPR will be in V's temporal dead zone. The actual syntax is for (let V1=EXPR1, V2=EXPR2,...;...) STMT Again, the semantics for such declarations is that all of the Vs are defined in the same scope and EXPRn+1 is outside the TDZ for for Vn. Using your semantics for the 3rd form would mean that its let clause was not consistent with let declarations in all other contexts. The behavior of let-declarations is that the name hoists to the enclosing block. We don't want to be *that* consistent. So the situation ends up being: we will have a statement that looks like for(let V = ...). It can either be partly consistent with other statements that look like for (let ...) or partly consistent with other statements that look like let V = Either choice breaks with consistency on one side; TC39 should pick the way that astonishes the fewest people. What is even more important than consistency is that the language work for people. We know that *not* having per-iteration bindings astonishes users, because that's how SpiderMonkey does it, and people are regularly astonished. I think you are over generalizing from a specific use case. This bug cuts both ways. Could you explain a little more why you think that's the case? Grepping finds 1,227 loops of the for (let ...; ...; ...) variety in Mozilla's codebase. Of these 95% are simple counting loops, and *none* contain functions in the loop-head. I estimate about 4% of these 1,227 loops have functions in the body, most of which escape, but it is harder to put precise numbers on that. Loops where the loop variables are modified in the loop body seem rare (I didn't see any), but I can't say how rare. Of the non-simple-counting loops, here is a sample: for (let child = element.firstChild; child; child = child.nextSibling) ... for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) ... for (let row; (row = aResultSet.getNextRow());) ... for (let i = 0; this[i] != null; i++) ... In all these cases, per-iteration bindings would do the right thing. If the choice comes down to astonishing this programmer: for (let i = 0; i n; i++) { buttons[i] = makeButton(); buttons[i].click(function () { alert(pushed button + i); }); } or astonishing this one: let geti; for (let i=(geti = function() {return i},expr), expr, incr = function(i++), decr=function(i--), cmp=function(){return in}; cmp();incr()) { let j=i; i +=10; decr(); ... If (j+-9 !== i) ... ... } then from where I'm sitting it looks like an easy choice. Let's support realistic use cases. Of course if per-iteration bindings would be confusing in practice, that would be bad. But the for-loop head issue should not be a major consideration, because people truly almost never write code like that. I'd be glad to have range(); however I don't think it's effective to address usability issues by offering more alternatives. :-\ -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 3, 2012, at 12:44 PM, Jason Orendorff wrote: On Fri, Feb 3, 2012 at 11:23 AM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: On Feb 3, 2012, at 6:42 AM, Jason Orendorff wrote: for (let V in EXPR) STMT for (let V of EXPR) STMT for (let V = EXPR; ...; ...) STMT the third case is different in several ways. Including: let V =EXPR is a distinct syntactic pattern that has a specific semantics that requires that the EXPR is evaluated in the same scope as EXPR. However, in most cases (but consider |et x=(x=5,++x);) all of EXPR will be in V's temporal dead zone. The actual syntax is for (let V1=EXPR1, V2=EXPR2,...;...) STMT Again, the semantics for such declarations is that all of the Vs are defined in the same scope and EXPRn+1 is outside the TDZ for for Vn. Using your semantics for the 3rd form would mean that its let clause was not consistent with let declarations in all other contexts. The behavior of let-declarations is that the name hoists to the enclosing block. We don't want to be *that* consistent. So the situation ends up being: we will have a statement that looks like for(let V = ...). It can either be partly consistent with other statements that look like for (let ...) or partly consistent with other statements that look like let V = I think that the best way to think about this is that all for statements with let/const declaration implicit introduce a block to contain those declarations. The base question here is the extent of that block. Does it surround the entire for statement, or just the statement part of the for statement. Or does it need two blocks, one surrounding the for and one surrounding the statement. The |let| is inside the |for| so I think |for| bounded hoisting is fairly consistent with general let declarations. Either choice breaks with consistency on one side; TC39 should pick the way that astonishes the fewest people. What is even more important than consistency is that the language work for people. We know that *not* having per-iteration bindings astonishes users, because that's how SpiderMonkey does it, and people are regularly astonished. I think you are over generalizing from a specific use case. This bug cuts both ways. Just to reinforce something I think I already said. I'm not questioning the desirability of per iteration bindings for for-in/for-of. I'm saying that in some cases (not most) that people who use the fully generality of the for(;;) statement will be astonished. I think this is even recognized by you in your desugaring by the fact that you use %tmp to propagate the value of V from one iteration to the next. It would be astonishing that a V++ in the loop body did not change the value of V seen by the next iteration. That desugarings work fine in that regard as long as there is no closure capture involved. But consider: for (let keepGoing=true, stopper=function() {keepGoing=false}; keepGoing;) { let thisOne = getNext(); if (thisOne == null) keepGoing = false; else thisOne.scanForSomeSpecificConditionAndIfDetectedDo(stopper); } There are lots of other ways to structure such a loop. But given the observable value propagation from iteration to iteration, it isn't unreasonable for a programmer to expect this to work. They will be surprised that it doesn't. Could you explain a little more why you think that's the case? Grepping finds 1,227 loops of the for (let ...; ...; ...) variety in Mozilla's codebase. Of these 95% are simple counting loops, and *none* contain functions in the loop-head. I estimate about 4% of these 1,227 loops have functions in the body, most of which escape, but it is harder to put precise numbers on that. Loops where the loop variables are modified in the loop body seem rare (I didn't see any), but I can't say how rare. It might also be useful to look at for(var ...;...;...) and for (...;...;...) loops. Nobody is proposing changing the scoping for those, but they might provide a broader view of how people use the generality of for(;;) loops Of the non-simple-counting loops, here is a sample: for (let child = element.firstChild; child; child = child.nextSibling) ... for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) ... for (let row; (row = aResultSet.getNextRow());) ... the above is a little odd for (let i = 0; this[i] != null; i++) ... In all these cases, per-iteration bindings would do the right thing. If the choice comes down to astonishing this programmer: for (let i = 0; i n; i++) { buttons[i] = makeButton(); buttons[i].click(function () { alert(pushed button + i); }); } I would actually feel a lot more comfortable with per iteration binding of for(;;) if the for let declaration was limited to a single binding. In that case, a programmer really has to go out of their way to closure capture the
Re: lexical for-in/for-of loose end
On Feb 3, 2012, at 4:26 PM, Jason Orendorff wrote: On 2/3/12 6:13 PM, Allen Wirfs-Brock wrote: But I also have to validly (and hopefully reasonably) specify exactly what happens for the unrealistic use cases. There is a problem with your desugaring in that the evaluation of INIT isn't scoped correctly relative to V. Hmmm. I don't see the problem yet. I think it's scoped the way I intended it: INIT is evaluated in the enclosing environment; V isn't in scope. Under the scoping rules TC39 has agreed to, the initializer of a let/const is always shadowed by the binding it is initializing: { let i=outer; { let i=i; // throws reference error because I is initialized } } Hence, for consistency, this also should throw in the same way { let i=outer; for (let i=i;;){} } You can tweak your desugaring to get this effect by changing the first part of it it: { let V = INIT; let %tmp = V, %first = true; while (true) { Anyway—all this should be easier to express in spec language than via desugaring, because in spec language you can give an environment a name. This makes it easier to just say what you mean. Whereas if you're using desugaring, scopes are *places*, so you have to very carefully say half of what you mean while standing in the right place to express the rest. agreed -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 1, 2012, at 7:13 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: On Feb 1, 2012, at 3:31 PM, Brendan Eich wrote: Brendan Eich wrote: ... That raises the question of the role of the for-in statement in the language (besides just being an obsolete and less useful alternative to for-of). Being a full fledged syntactic statement of the language, it certainly isn't hiding in an obscure corner. If we want to think of it as something other than an over exposed reflection tool, what is it for?. The best expression of this that I can come up with, is that for-in is a statement that is primarily useful for iterating the data member keys of simple record and array like data abstractions. I probably should have included particularly those created using JSON.parse at the end of the last sentence above. So far, so good, and latent here is the best case for not elaborating the left-hand side to include destructuring, if you can avoid bloating the grammar too much. Contra my regularity plus small use-cases argument, we leave for-in alone. We can even leave it underspecified as in ES5. but still loose the initializer on var, right? Also, for consistency I still allow let/const in place or var with with fresh bindings on each iterations. These are really orthogonal issues relating to the new blocked scoped declaration and we should handle them constantly everywhere. From that perspective, own-ness again is something that should primarily be an implementation concern. Historically this came up because pre-ES5 people couldn't extend Array.prototype, e.g., without making enumerable properties. But of course then the wisdom said don't use for-in on arrays. Object.prototype is verboten, and at some cost in using custom functional-programming style iteration, we're ok (PrototypeJS status quo). The problem that may remain is that for own (k in o) ...; still beats for (k in keys(o)) ...; not only by a couple of chars not counting import or assuming prelude, but in terms of people's historical memory and folk-wisdom learning. I would think we could make this for (k in own(o)) ...; and for (k of own(o)) ...; if the object produced by own() provides appropiate definitions for [[Enumerate]] and [[Iterate]] Presumably, the only prior leaning that applies comes from CoffeeScript. While CS is popular, it isn't at all close to being used by the majority of JS developers. CS experience is informative but I don't think we have to worry so much about CS derived habits. Should we ignore all this and say just use for-of with the right iterator? probably... To me, for-own-in makes it too easy for client code to create dependencies upon what should inconsequential implementation decisions. In reality the shoe is on the other foot. Implementation decisions that should be without consequence mess up for-in, requiring hasOwnProperty testing. People want a shorter-path solution. Giving a longer-path solution with for-of and iterators is good, we want that. Is it good enough to be the only solution we build into the language? If we agree that for-in is the problem, then it seems like we should be trying to make for-of more attractive while at the same time making for-in less attractive (or at least not improving for-in in a way that also makes it more attractive) We have a goal of making ES a language that is better for creating and using abstractions. For-own-in is a tool that tunnels through abstractions. I think you're making too sweeping a statement about abstractions. Does for-own-in tunnel in ways that violate abstractions, or is it really what the doctor ordered for JS close to today's best practices (not always followed)? I say more the latter, but not clear cut in any event. JS today isn't a very good language for building abstractions of the sort that are useful for structuring complex applications and correcting that is one of our goals for Harmony. Today's JS best practices don't really reflect this perspective and we should be careful about enshrining backwards looking practices. Such a tool is fine as part of the reflection facility, but making it a full fledged statement seems like it would be creating an attractive nuisance. We have a full-fledged statement, for-in. It can't be deprecated quite yet. Adding for-of helps, but is it enough? That's the issue I'm raising. ... Our current position is use for-of and an items helper function: import items from @reflect; // currently @iter; could be part of standard prelude for ([k, v] of items(o)) ...; see the main part of the message I referenced above. Pure functional filters like item are unfriendly to collection abstracton builders. JS has little of the OO collection building you see in Smalltalk or Java. Is this a bug in the language and communities using it? I'm not so sure. I think this is primarily a
Re: lexical for-in/for-of loose end
On Feb 1, 2012, at 7:19 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: water under the bridge... Sure, but it matters for distinguishing my inconsistent advocacy of removing the hated initialiser, vs. keeping destructuring in for-in LHS. Really, I'm ok with not supporting destructuring for-in LHS. That will require more spec work, but so does killing the initialiser for let and const as you noted. So, are we agreed? No initialiser, no for-in destructuring (with or without a declaring keyword)? sounds good to me, see other message /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Maybe ban holes, then... (was: Re: lexical for-in/for-of loose end)
Allen Wirfs-Brock wrote: That said, I'd hate to see things like: [,x] = someArray; and even [a,,y] = someArray; can be easily missed. It isn't clear to me that we are doing ES programmers (especially those with poor eyesight) a favor by allowing such expressions. What is the evidence with JS1.7 up. How much usage do we see of such holes? If {5:x} = someArray; and {0:a, 2:y} = someArray; would be possible, there is probably little need for holes. In case of [a,b,c,d,,f] = someArray; one could always use (some conventional) anon-var, like _ (from Prolog): [a,b,c,d,_,f] = someArray; Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Maybe ban holes, then... (was: Re: lexical for-in/for-of loose end)
This is all unwanted innovation. We have holes in arrays, and in SpiderMonkey and Rhino we've had holes in destructuring array patterns for years. No one got confused or went blind. We should stop nanny-ing about holes. Some of you don't like holes, but they're not going away in arrays. They are useful in array patterns to avoid _ or junk bindings. That's enough to keep them, rather than straining to invent more cumbersome ad-hoc replacements. /be Herby Vojčík mailto:he...@mailbox.sk February 2, 2012 11:49 AM If {5:x} = someArray; and {0:a, 2:y} = someArray; would be possible, there is probably little need for holes. In case of [a,b,c,d,,f] = someArray; one could always use (some conventional) anon-var, like _ (from Prolog): [a,b,c,d,_,f] = someArray; Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Maybe ban holes, then... (was: Re: lexical for-in/for-of loose end)
Allen Wirfs-Brock wrote: It isn't just possible, it is already specified as valid. and youdon't need the quotes around the array indices. But you need a set ofparams to deal with the expression statement can't start with { problem. This wouldn't be the case for binding position destructuring. ({5:x} = someArray); ({0:a, 2:y} = someArray); A bit off-topic... I don't like paretheses. Not only because I have to put them in both ends, but also because of uncomfortable feeling of thing closed in parentheses. Maybe alternative convention to deal with expressions beginning with { could be used as well: 0, {5:x} = someArray; 0, {0:a, 2:y} = someArray; Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 1, 2012, at 7:13 PM, Brendan Eich wrote: So far, so good, and latent here is the best case for not elaborating the left-hand side to include destructuring, if you can avoid bloating the grammar too much. Contra my regularity plus small use-cases argument, we leave for-in alone. We can even leave it underspecified as in ES5. but still loose the initializer on var, right? I think so. We can try. I'll see if Oliver and Gavin are game to try in JSC. Also, for consistency I still allow let/const in place or var with with fresh bindings on each iterations. These are really orthogonal issues relating to the new blocked scoped declaration and we should handle them constantly everywhere. Yes! Good news is we agreed (most of us, it was late the second day of last month's TC39 meeting) to make for(;;) have the same fresh binding per iteration. From that perspective, own-ness again is something that should primarily be an implementation concern. Historically this came up because pre-ES5 people couldn't extend Array.prototype, e.g., without making enumerable properties. But of course then the wisdom said don't use for-in on arrays. Object.prototype is verboten, and at some cost in using custom functional-programming style iteration, we're ok (PrototypeJS status quo). The problem that may remain is that for own (k in o) ...; still beats for (k in keys(o)) ...; not only by a couple of chars not counting import or assuming prelude, but in terms of people's historical memory and folk-wisdom learning. I would think we could make this for (k in own(o)) ...; and for (k of own(o)) ...; keys etc. are currently spec'ed to to do own-only, you want allKeys to do the crazy thing: http://wiki.ecmascript.org/doku.php?id=harmony:iterators#standard_api Let's not go around and around on this. Enumerable proto-properties are harmful, no one wants them. if the object produced by own() provides appropiate definitions for [[Enumerate]] and [[Iterate]] Jason's cheat-sheet (I cited it fully) shows good enough sugar if you do the import, or we agree on the prelude. Presumably, the only prior leaning that applies comes from CoffeeScript. While CS is popular, it isn't at all close to being used by the majority of JS developers. CS experience is informative but I don't think we have to worry so much about CS derived habits. It's informative, agreed, and competitive -- my point. TC39 does poorly inventing new and untested API. We do better paving cowpaths. I think we should be careful not to get too far ahead of de-facto standards, while avoiding being hostage to language design flaws from the past. We also should not oversell Smalltalk, etc. -- Smalltalk is informative (your collections port especially) but not particularly more important (or less) than CS. Should we ignore all this and say just use for-of with the right iterator? probably... Ok. Is this what you are doing (modulo the initialiser removal)? I assume that you are arguing that the default @iterator for Object.prototype (however it is provided) No! I clearly said there is no @iterator in Object.prototype. does a key enumeration just like ES1-5 for-in. Or are you arguing that it produces nothing? No @iterator in Object.prototype. We've been over this, Jason argued convincingly against pre-defining one because it is future-hostile to collection iteration. I just need to find the right thread... Me too. There is still a default. The iterators proposal says that for-of falls back to @enumerate if @iterate isn't present. That's a proxy trap, and I believe that dates from before for-of. We need to revisit this now, based on splitting iteration out to for-of and leaving for-in the old enumerating mystery meat that it has always been. Cc'ing Tom. It's important to distinguish property default from proxy trap built-in. If there's an Object.prototype.@iterator that iterates values, or [key,value] pairs, or whatever, then we make it hard for new collections and set-like abstractions to do something slightly different. BTW, this is a good example of something that should not be built into statement semantics. Why should |for (v or values(o))...| iterate differently than |values(o).forOf({|v|...})| Do you mean forEach? Who says values(o) returns an array? That is costly and the point of iterators is to use a lazy protocol. Indeed the http://wiki.ecmascript.org/doku.php?id=harmony:iterators#standard_api proposal has values a generator (returns an iterator). My currently thinking (working off of the current wiki proposal) is to have [[Enumerate]] and [[Iterate]] internal methods (for all objects) and it is in their default implementation that [[Iterate]] delegates to [[Enumerate]]. No, see above. We do not want to mix these up. Iteration via for-of uses the iteration protocol. No issue here, I think. We want for (x of {p:1, q:2}) to
Re: Maybe ban holes, then... (was: Re: lexical for-in/for-of loose end)
On 2 February 2012 21:19, Brendan Eich bren...@mozilla.org wrote: Some of you don't like holes, but they're not going away in arrays. They are useful in array patterns to avoid _ or junk bindings. That's enough to keep them, rather than straining to invent more cumbersome ad-hoc replacements. Are you implying that wildcards are more ad-hoc than array holes? That doesn't add up for me. /Andreas ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 2, 2012, at 1:38 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: On Feb 1, 2012, at 7:13 PM, Brendan Eich wrote: ... Unfortunately, having thought about it a bit since the meeting, I'm less convinced. We didn't look too deeply at possible capture scenarios such as: let geti; for (let i=(geti = function() {return i},expr), expr, incr = function(i++), decr=function(i--), cmp=function(){return in}; cmp();incr()) { let j=i; i +=10; decr(); ... If (j+-9 !== i) ... ... } ... keys etc. are currently spec'ed to to do own-only, you want allKeys to do the crazy thing: http://wiki.ecmascript.org/doku.php?id=harmony:iterators#standard_api Let's not go around and around on this. Enumerable proto-properties are harmful, no one wants them. I'm afraid I have to disagree with the absolutism of this statement. There are some perfectly reasonable use cases for enumerating proto properties and they are quite in alignment with prototypal inheritance. For example, how about defining a set of default property values and then using prototypal inheritance to selective over-ride them: let privateImplicitOptions = {a:1,b:2,c:3 /* and on and on */}; function makeOptions(explicitOptions) { let opt = Object.create(privateImplicitOptions); for (let [k.,v] of items(explicitOptions)) opt[k]=v; return opt; } myOpts = makeOptions({b:-2}); Why should somebody I pass an options object to need to worry about whether I used zero, one, or n levels of prototypes to represent by options. that said, I think we agree that the real problem is the legacy ES semantics of for-in and its use (or lack of) for the enumerable attribute. If we can start out with a clean slate conceptual slate for for-of maybe we don't have to worry about such things. However, carrying over a lot of historic baggage isn't a clean slate. Thinking in terms of abstract collections, having keys() not mean all keys in the collection seems quite absurd. That should be the normal case, the more verbose name should be used for the exceptional case that excludes some of the keys. Now in this case, the real problem is in deciding the default way to view a plain Es objects as an abstract collection. Maybe only considering own properties as the collection elements is the right thing. In that case it may be allKeys that is carrying the wrong implication and instead it should be something like extendedKeys. ... Presumably, the only prior leaning that applies comes from CoffeeScript. While CS is popular, it isn't at all close to being used by the majority of JS developers. CS experience is informative but I don't think we have to worry so much about CS derived habits. It's informative, agreed, and competitive -- my point. TC39 does poorly inventing new and untested API. We do better paving cowpaths. I think we should be careful not to get too far ahead of de-facto standards, while avoiding being hostage to language design flaws from the past. We also should not oversell Smalltalk, etc. -- Smalltalk is informative (your collections port especially) but not particularly more important (or less) than CS. Totally agree concerning Smalltalk other languages. However, when we are looking to extend into new areas it makes sense to look at languages that excel in those areas. We're talking about making ES a better to define object-based abstractions and in specifically in this thread collection abstractions. That is an area where we can learn a lot from Smalltalk. ... There is still a default. The iterators proposal says that for-of falls back to @enumerate if @iterate isn't present. That's a proxy trap, and I believe that dates from before for-of. We need to revisit this now, based on splitting iteration out to for-of and leaving for-in the old enumerating mystery meat that it has always been. Cc'ing Tom. It's important to distinguish property default from proxy trap built-in. Not sure, I understand why. Presumably the default behavior of a proxy trap should be the same as the built-in behavior. The internal methods (which need to be extended to include things like [[Interator]]) have a specification for normal objects and an alternate one for Proxy objects that just invokes the trap. However, the default traps handlers should be specified to turn around and invoke the corresponding normal internal method. If there's an Object.prototype.@iterator that iterates values, or [key,value] pairs, or whatever, then we make it hard for new collections and set-like abstractions to do something slightly different. I don't see where the hardness comes from. They just need to define an appropaite @Interator method. They are going to have to do so regardless of whether a default @Iterator is defined on Object.prototype or whether the default is just hardwired (for example to throw). In either case the default needs to be
Re: lexical for-in/for-of loose end
On Thu, Feb 2, 2012 at 5:52 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Unfortunately, having thought about it a bit since the meeting, I'm less convinced. We didn't look too deeply at possible capture scenarios such as: let geti; for (let i=(geti = function() {return i},expr), expr, incr = function(i++), decr=function(i--), cmp=function(){return in}; cmp();incr()) { let j=i; i +=10; decr(); ... If (j+-9 !== i) ... ... } What?! We should not reward people writing code like that. I would be fine with the initializer-expression not being in the scope of i at all. -j ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 2, 2012, at 5:07 PM, Jason Orendorff wrote: On Thu, Feb 2, 2012 at 5:52 PM, Allen Wirfs-Brock al...@wirfs-brock.com wrote: Unfortunately, having thought about it a bit since the meeting, I'm less convinced. We didn't look too deeply at possible capture scenarios such as: let geti; for (let i=(geti = function() {return i},expr), expr, incr = function(i++), decr=function(i--), cmp=function(){return in}; cmp();incr()) { let j=i; i +=10; decr(); ... If (j+-9 !== i) ... ... } What?! We should not reward people writing code like that. I would be fine with the initializer-expression not being in the scope of i at all. No reward, it is simply what the current syntax permits, and it is important that semantics are consistently applied. The initialization semantics I assume here are exactly the same that would apply if if the let clause of the for was pulled out and used as a standalone statement. In general, excluding i from the visibility of the initializer expression would prevent writing recursive functions definitions such as: const fact = function (n) {return n=1 ? 1 : n*fact(n-1)}; for-in works well with a per iteration binding because there is no header code that runs between iterations. The various desugarings that have been proposed to provide a fresh copy for each iteration of this form of for work for propagating simple values between the header and each iteration but not for closure captures. Plus, the desugarings aren't things that are really suitable for teaching the semantics to everyday JS programmers. You instead have to say something like: Ok, this is really complicated but here goes. For each let/const declared in the for header, a fresh variable is located in the loop body for each iteration of the loop. However, the values of the loop variables are automatically copied from the previous iteration into the next iterations. This means that basic expression operator will work in the loop header pretty much like you would expect. But be careful if you use any function expressions in the for header because the loop variables they reference may not be from the current iteration and any changes to loop variable they make may not have the effect you intended. But, hey you probably shouldn't do those things so it really doesn't matter what it really does. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
I did a code search for for (var ident = before code search was shut down and the only code it found was from test suites. On Jan 31, 2012 6:31 AM, Nadav Shesek na...@shesek.info wrote: On Jan 31, 2012 3:38 PM, Sam Tobin-Hochstadt sa...@ccs.neu.edu wrote: Google Code Search is no longer available, sadly. Oh, I didn't know it was shutdown. Hopefully some Google internals can still use that data to give some stats when those questions arise... ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
I think it would be semantically correct to allow at least let there as it is for loops for (let i = 0; i length; i++); // OK for (let key in obj); // not OK ? weird, I know destructuration is OK but for/in are still for loops my 2 cents, br ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
Sorry, the search was a lot more elaborate to include in too. On Feb 1, 2012 9:29 AM, Erik Arvidsson erik.arvids...@gmail.com wrote: I did a code search for for (var ident = before code search was shut down and the only code it found was from test suites. On Jan 31, 2012 6:31 AM, Nadav Shesek na...@shesek.info wrote: On Jan 31, 2012 3:38 PM, Sam Tobin-Hochstadt sa...@ccs.neu.edu wrote: Google Code Search is no longer available, sadly. Oh, I didn't know it was shutdown. Hopefully some Google internals can still use that data to give some stats when those questions arise... ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Another loose end. With the addition of for-of, for-in reverts back to always iterating over the property keys of an object as it does not support the generalized iteration protocols. An implication of this is that using a de-structuring pattern as the iteration variable has very limited utility: for (let [c] in obj) print(c); //print the first character of each of obj's enumerable property names for (const {length:len} in obj) print(len); print the length of each of obj's enumerable property names Given this lack of utility, why should we allow de-structuring in this context? Eliminating it arguably increases language complexity by introducing in a bit more grammar irregularity. On the other hand, eliminating useless functionality can be seen as as simplification. I'm leaning towards banning destructing in for-in. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: Another loose end. With the addition of for-of, for-in reverts back to always iterating over the property keys of an object as it does not support the generalized iteration protocols. An implication of this is that using a de-structuring pattern as the iteration variable has very limited utility: for (let [c] in obj) print(c); //print the first character of each of obj's enumerable property names for (const {length:len} in obj) print(len); print the length of each of obj's enumerable property names Given this lack of utility, why should we allow de-structuring in this context? These are not totally silly examples. I say when in doubt, let language regularity win. We are also thereby future-friendly in case some evolution of property keys becomes even more structured. I know, unlikely, but again: regularity when in doubt. Eliminating it arguably increases language complexity by introducing in a bit more grammar irregularity. On the other hand, eliminating useless functionality can be seen as as simplification. Not useless, your own examples show use-cases. I'm leaning towards banning destructing in for-in. Too nannyish! /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 1, 2012, at 2:13 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: Another loose end. With the addition of for-of, for-in reverts back to always iterating over the property keys of an object as it does not support the generalized iteration protocols. An implication of this is that using a de-structuring pattern as the iteration variable has very limited utility: for (let [c] in obj) print(c); //print the first character of each of obj's enumerable property names for (const {length:len} in obj) print(len); print the length of each of obj's enumerable property names Given this lack of utility, why should we allow de-structuring in this context? These are not totally silly examples. I say when in doubt, let language regularity win. Well, they're at least 98% silly and these were the only even semi-plausable example I could think of. We are also thereby future-friendly in case some evolution of property keys becomes even more structured. I know, unlikely, but again: regularity when in doubt. In which case, it would be easy enough to allow them in the future. It's always easer to relax a restriction then it is to add one. Eliminating it arguably increases language complexity by introducing in a bit more grammar irregularity. On the other hand, eliminating useless functionality can be seen as as simplification. Not useless, your own examples show use-cases. I'm leaning towards banning destructing in for-in. Too nannyish! I don't really see the consistency in trying to ban the initializer in for (var k=42 in obj) ; because it is a awful, lazy-grammar-resue error. While adding for (var [c] in obj) ; I don't actually feel too strongly either way and it is actually more work for me to disallow the destructuring pattern. However, I am trying to find some basis for consistency in the design choices we make. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 1, 2012, at 2:13 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: Another loose end. With the addition of for-of, for-in reverts back to always iterating over the property keys of an object as it does not support the generalized iteration protocols. An implication of this is that using a de-structuring pattern as the iteration variable has very limited utility: for (let [c] in obj) print(c); //print the first character of each of obj's enumerable property names for (const {length:len} in obj) print(len); print the length of each of obj's enumerable property names Given this lack of utility, why should we allow de-structuring in this context? These are not totally silly examples. I say when in doubt, let language regularity win. Well, they're at least 98% silly and these were the only even semi-plausable example I could think of. So you agree with my point about regularity + non-silly use-cases winning? :-/ We are also thereby future-friendly in case some evolution of property keys becomes even more structured. I know, unlikely, but again: regularity when in doubt. In which case, it would be easy enough to allow them in the future. It's always easer to relax a restriction then it is to add one. How would you restrict them? Splitting productions, or semantic restrictions? It matters, not just aesthetically but for implementation simplicity. I don't really see the consistency in trying to ban the initializer in for (var k=42 in obj) ; because it is a awful, lazy-grammar-resue error. ES1 was based on real implementations. The Netscape one did not allow an initaliser here. There's no good use for it compared to destructuring key lengths or substrings, especially not with var (which hoists). You want an initial var value to survive a zero-iteration loop? Hoist the var. While adding for (var [c] in obj) ; Are you really adding? It depends on good vs. bad reuse of sub-grammar. I appealed to regularity. That means standard rules for conjugating verbs, etc., in natural languages. In programming languages one would hope that binding forms would compose the same with for-in as for-of as for;; and other binding contexts, modulo the initialiser. At least, that's what users of JS1.7-and-up have hoped. That evidence is tainted because we did make for-in programmable via an iteration protocol, as for-of is in ES6. Granted. But unless there's an actual hardship in regularity (I don't believe there is with for-in, but I welcome evidence from you on spec front, and other implementors on impl front) I still think regularity wins. I don't actually feel too strongly either way and it is actually more work for me to disallow the destructuring pattern. I'm not super-strong on this, don't get me wrong. It's much less of an issue either way than banning the initialiser for let and const. I hope we agree on that much, as a partial order of preferences or strong convictions! It also seems relevant that you'd have to do more work in the spec, and presumably make the grammar bigger, to disallow destructuring (with and without var/let/const) in for-in. More spec effort and grammar size make for errata habitat. Just as with code. However, I am trying to find some basis for consistency in the design choices we make. Again, for-in as designed in JS1 never allowed an initialiser and it's an unwanted feature added to match JScript or (perhaps JScript too lacked it) to reuse part of the grammar a certain way. Note that the reuse of VariableDeclarationNoIn didn't save anything in the grammar, so I bet the issue was JScript allowing an initialiser. Anyone know? It's never inconsistent to allow one thing and disallow another. The particulars matter. This isn't anything goes. Destructuring has a bit of utility and a lot of regularity in for-in head position. The initialiser from VariableDeclarationNoIn has neither specific utility nor regularity with respect to for-in in AWK, Python, or any other language with such a construct. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich wrote: It's never inconsistent to allow one thing and disallow another. The particulars matter. This isn't anything goes. Destructuring has a bit of utility and a lot of regularity in for-in head position. The initialiser from VariableDeclarationNoIn has neither specific utility nor regularity with respect to for-in in AWK, Python, or any other language with such a construct. More to say, I sent too soon. Don't take this as more than it is: an attempt to explore an alternative meaning of for-in combined with certain destructuring patterns. We have a request that seemed to receive popular support, to support for own (k in o) ...; This could compose with the following nicely, and it tries to pave the CoffeeScript cowpath a bit (without taking CoffeeScript as normative or overriding or anything like that). We could make just-so meanings for destructuring in for-in, also inspired by CoffeeScript (and JS1.7, which did this too while muddying the waters by failing to separate iteration protocol into for-of): for ([k, v] in o) ...; Our current position is use for-of and an items helper function: import items from @reflect; // currently @iter; could be part of standard prelude for ([k, v] of items(o)) ...; But I think detailing the design of ES6 must be allowed to entertain an even easier-to-use extension to for-in. If you want values not keys, then per the current proposal you use for (v of values(o)) ...; given the necessary values import, or inclusion in a standard prelude. There's a hole vs. CoffeeScript: we do not want for-of on a plain Object to iterate over enumerable property values. Arrays, yes, Objects, no -- no Object.prototype.@iterator. So no |for (v of o) ...| for o = {p:1, q:2, r:3}. Also no for each (v in o) as E4X (ECMA-357) promulgated. SpiderMonkey and Rhino support it and probably will have to carry it, but such each does not say its meaning (values not keys) clearly, and it doesn't compose with own nicely. But with destructuring for-in, you could write for ([, v] in o) ...; This is a bit ugly (holes never look pretty, even when they're useful). Not sure what I think of this but I thought I'd throw it out here on es-discuss. The reason I bring it up is twofold: 1) for own (k in o) still needs to be discussed; 2) the for ([k, v] of items(o)) ...; tax is a bit higher than I'd like. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Feb 1, 2012, at 3:31 PM, Brendan Eich wrote: Brendan Eich wrote: It's never inconsistent to allow one thing and disallow another. The particulars matter. This isn't anything goes. Destructuring has a bit of utility and a lot of regularity in for-in head position. The initialiser from VariableDeclarationNoIn has neither specific utility nor regularity with respect to for-in in AWK, Python, or any other language with such a construct. More to say, I sent too soon. Don't take this as more than it is: an attempt to explore an alternative meaning of for-in combined with certain destructuring patterns. We have a request that seemed to receive popular support, to support for own (k in o) ...; This could compose with the following nicely, and it tries to pave the CoffeeScript cowpath a bit (without taking CoffeeScript as normative or overriding or anything like that). Towards the end of https://mail.mozilla.org/pipermail/es-discuss/2011-November/018332.html I made an argument that own-ness of properties is an implementation choice that normally should not be of concern to application clients of an object-based abstraction. Own-ness is primarily relevant to abstraction implementors or somebody who is using reflection to examine the implementation structure of an object. Hopefully reflection is tucked away in a obscure but pleasant corner of the language and not widely used for routine application logic. Generally, reflection doesn't deserve (or require) statement level support. That raises the question of the role of the for-in statement in the language (besides just being an obsolete and less useful alternative to for-of). Being a full fledged syntactic statement of the language, it certainly isn't hiding in an obscure corner. If we want to think of it as something other than an over exposed reflection tool, what is it for?. The best expression of this that I can come up with, is that for-in is a statement that is primarily useful for iterating the data member keys of simple record and array like data abstractions. From that perspective, own-ness again is something that should primarily be an implementation concern. Clients who are iterating over my public data members shouldn't be concerned about whether or not I decided to implement my data abstraction using a tree of objects or be impacted if I change my mind. To me, for-own-in makes it too easy for client code to create dependencies upon what should inconsequential implementation decisions. We have a goal of making ES a language that is better for creating and using abstractions. For-own-in is a tool that tunnels through abstractions. Such a tool is fine as part of the reflection facility, but making it a full fledged statement seems like it would be creating an attractive nuisance. We could make just-so meanings for destructuring in for-in, also inspired by CoffeeScript (and JS1.7, which did this too while muddying the waters by failing to separate iteration protocol into for-of): for ([k, v] in o) ...; Clearly, this isn't a general destructuring. It is a special syntactic form that is mimicking array destructuring syntax. We could probably have a long thread about whether such mimicry is clever language design or a confusing creole. I reserve my opinion on the general questions. Our current position is use for-of and an items helper function: import items from @reflect; // currently @iter; could be part of standard prelude for ([k, v] of items(o)) ...; see the main part of the message I referenced above. Pure functional filters like item are unfriendly to collection abstracton builders. But I think detailing the design of ES6 must be allowed to entertain an even easier-to-use extension to for-in. Another simplification would be to make default iterator produce {key,value} item objects, Then we have three concise formulations: for ({key, value} of o) ...; for ({key} of o) ...; for ({value} of o) ... If you want values not keys, then per the current proposal you use for (v of values(o)) ...; given the necessary values import, or inclusion in a standard prelude. There's a hole vs. CoffeeScript: we do not want for-of on a plain Object to iterate over enumerable property values. Arrays, yes, Objects, no -- no Object.prototype.@iterator. So no |for (v of o) ...| for o = {p:1, q:2, r:3}. (I note at this point that there are some huge es-discuss threads on topics such as the above for which the conclusions (if any) have not been extracted. I'm starting to go over them to see what I can extract from them) I assume that you are arguing that the default @iterator for Object.prototype (however it is provided) does a key enumeration just like ES1-5 for-in. Or are you arguing that it produces nothing? I'm not exactly sure why either of those is better than |for (v of o) ...| but I think there are a couple threads that I
Re: lexical for-in/for-of loose end
On Feb 1, 2012, at 3:05 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: On Feb 1, 2012, at 2:13 PM, Brendan Eich wrote: Allen Wirfs-Brock wrote: Another loose end. With the addition of for-of, for-in reverts back to always iterating over the property keys of an object as it does not support the generalized iteration protocols. An implication of this is that using a de-structuring pattern as the iteration variable has very limited utility: for (let [c] in obj) print(c); //print the first character of each of obj's enumerable property names for (const {length:len} in obj) print(len); print the length of each of obj's enumerable property names Given this lack of utility, why should we allow de-structuring in this context? These are not totally silly examples. I say when in doubt, let language regularity win. Well, they're at least 98% silly and these were the only even semi-plausable example I could think of. So you agree with my point about regularity + non-silly use-cases winning? :-/ I like regularity, which was why I was content to keep the 98+% silly initialization expression in for-in |for (var k=debugLog(starting loop) in obj)...|. keeps it all together for refactoring :-\ But, in reality I was probably more concerned about the breaking change issue. We are also thereby future-friendly in case some evolution of property keys becomes even more structured. I know, unlikely, but again: regularity when in doubt. In which case, it would be easy enough to allow them in the future. It's always easer to relax a restriction then it is to add one. How would you restrict them? Splitting productions, or semantic restrictions? It matters, not just aesthetically but for implementation simplicity. In this particular case I would probably use separate productions (already did that to eliminate the initializers for |for (let/const x in)|. In general, I prefer to only use static semantic restrictions only when they involve non-nearby contextual information about the program. However, that is a specification aesthetic. I don't see why an implementation parser would necessarily want to make the same grammar trade-offs. Converting grammar to static checks and visa versa seems like very routine parser engineering. I don't really see the consistency in trying to ban the initializer in for (var k=42 in obj) ; because it is a awful, lazy-grammar-resue error. ES1 was based on real implementations. The Netscape one did not allow an initaliser here. There's no good use for it compared to destructuring key lengths or substrings, especially not with var (which hoists). You want an initial var value to survive a zero-iteration loop? Hoist the var. While adding for (var [c] in obj) ; Are you really adding? It depends on good vs. bad reuse of sub-grammar. I appealed to regularity. That means standard rules for conjugating verbs, etc., in natural languages. Well, its adding to the grammar I wrote this morning... I already have to have additional productions (or static semantic rules, they amount to the same thing) that don't have initializer expressions on the for (let/const forms. But I like standard rules too. Too many special cases make the language harder to learn. But regularity that allow for WTF situations also isn't good. There is a sweat spot that needs to be found. In programming languages one would hope that binding forms would compose the same with for-in as for-of as for;; and other binding contexts, modulo the initialiser. At least, that's what users of JS1.7-and-up have hoped. That evidence is tainted because we did make for-in programmable via an iteration protocol, as for-of is in ES6. Granted. But unless there's an actual hardship in regularity (I don't believe there is with for-in, but I welcome evidence from you on spec front, and other implementors on impl front) I still think regularity wins. I actually think may two use cases for for-in de-structuring (as currently defined) are pretty close to WTF scenarios. Most people are going to have to stop and think carefully about what they mean. Probably more important, allowing destructuing in for-in is likely to further confuse people regularly forget that it iterates over keys rather than values. I don't actually feel too strongly either way and it is actually more work for me to disallow the destructuring pattern. I'm not super-strong on this, don't get me wrong. It's much less of an issue either way than banning the initialiser for let and const. I hope we agree on that much, as a partial order of preferences or strong convictions! It also seems relevant that you'd have to do more work in the spec, and presumably make the grammar bigger, to disallow destructuring (with and without var/let/const) in for-in. More spec effort and grammar size make for
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: On Feb 1, 2012, at 3:31 PM, Brendan Eich wrote: Brendan Eich wrote: It's never inconsistent to allow one thing and disallow another. The particulars matter. This isn't anything goes. Destructuring has a bit of utility and a lot of regularity in for-in head position. The initialiser from VariableDeclarationNoIn has neither specific utility nor regularity with respect to for-in in AWK, Python, or any other language with such a construct. More to say, I sent too soon. Don't take this as more than it is: an attempt to explore an alternative meaning of for-in combined with certain destructuring patterns. We have a request that seemed to receive popular support, to support for own (k in o) ...; This could compose with the following nicely, and it tries to pave the CoffeeScript cowpath a bit (without taking CoffeeScript as normative or overriding or anything like that). Towards the end of https://mail.mozilla.org/pipermail/es-discuss/2011-November/018332.html I made an argument that own-ness of properties is an implementation choice that normally should not be of concern to application clients of an object-based abstraction. Own-ness is primarily relevant to abstraction implementors or somebody who is using reflection to examine the implementation structure of an object. That's an ideal but the reality is for-in and easy transpilation from better derived forms to it are with us, and will be for a while. Hopefully reflection is tucked away in a obscure but pleasant corner of the language and not widely used for routine application logic. Generally, reflection doesn't deserve (or require) statement level support. still want easy array-value iteration. That's what for-of does out of the box. This is important, it shouldn't take any more chars than for (v of a)... eliding declaration of v and decl/init of array a. That raises the question of the role of the for-in statement in the language (besides just being an obsolete and less useful alternative to for-of). Being a full fledged syntactic statement of the language, it certainly isn't hiding in an obscure corner. If we want to think of it as something other than an over exposed reflection tool, what is it for?. The best expression of this that I can come up with, is that for-in is a statement that is primarily useful for iterating the data member keys of simple record and array like data abstractions. So far, so good, and latent here is the best case for not elaborating the left-hand side to include destructuring, if you can avoid bloating the grammar too much. Contra my regularity plus small use-cases argument, we leave for-in alone. We can even leave it underspecified as in ES5. From that perspective, own-ness again is something that should primarily be an implementation concern. Historically this came up because pre-ES5 people couldn't extend Array.prototype, e.g., without making enumerable properties. But of course then the wisdom said don't use for-in on arrays. Object.prototype is verboten, and at some cost in using custom functional-programming style iteration, we're ok (PrototypeJS status quo). The problem that may remain is that for own (k in o) ...; still beats for (k in keys(o)) ...; not only by a couple of chars not counting import or assuming prelude, but in terms of people's historical memory and folk-wisdom learning. Should we ignore all this and say just use for-of with the right iterator? To me, for-own-in makes it too easy for client code to create dependencies upon what should inconsequential implementation decisions. In reality the shoe is on the other foot. Implementation decisions that should be without consequence mess up for-in, requiring hasOwnProperty testing. People want a shorter-path solution. Giving a longer-path solution with for-of and iterators is good, we want that. Is it good enough to be the only solution we build into the language? We have a goal of making ES a language that is better for creating and using abstractions. For-own-in is a tool that tunnels through abstractions. I think you're making too sweeping a statement about abstractions. Does for-own-in tunnel in ways that violate abstractions, or is it really what the doctor ordered for JS close to today's best practices (not always followed)? I say more the latter, but not clear cut in any event. Such a tool is fine as part of the reflection facility, but making it a full fledged statement seems like it would be creating an attractive nuisance. We have a full-fledged statement, for-in. It can't be deprecated quite yet. Adding for-of helps, but is it enough? That's the issue I'm raising. We could make just-so meanings for destructuring in for-in, also inspired by CoffeeScript (and JS1.7, which did this too while muddying the waters by failing to separate iteration protocol into for-of): for ([k, v] in o) ...; Clearly, this
Google indexing code patterns (Re: lexical for-in/for-of loose end)
Erik Arvidsson wrote: I agree with Brendan, I think we can get rid of the initializer even for for-in with var. The only code I've seen that uses this is in ES test suites. Just an idea. Google crawls over the whole (crawler-accessible) web. That means it is indexing a _lot_ of .js files out there. I saw questions like the one above appear more times on this list. How often is [insert-your-own-quirk] used / is it used ever? Code files are of defined structure, unlike people-written documents. If Google would be able to index code files with some added information that allows to search in that structure, it could yield important information about usage patterns of this or that programming language. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
Google's code search allows searching for regular expressions. Perhaps that could be useful. Sent from Android On Jan 31, 2012 11:41 AM, Herby Vojčík he...@mailbox.sk wrote: Erik Arvidsson wrote: I agree with Brendan, I think we can get rid of the initializer even for for-in with var. The only code I've seen that uses this is in ES test suites. Just an idea. Google crawls over the whole (crawler-accessible) web. That means it is indexing a _lot_ of .js files out there. I saw questions like the one above appear more times on this list. How often is [insert-your-own-quirk] used / is it used ever? Code files are of defined structure, unlike people-written documents. If Google would be able to index code files with some added information that allows to search in that structure, it could yield important information about usage patterns of this or that programming language. Herby __**_ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/**listinfo/es-discusshttps://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
Google Code Search is no longer available, sadly. On Tue, Jan 31, 2012 at 6:20 AM, Nadav Shesek na...@shesek.info wrote: Google's code search allows searching for regular expressions. Perhaps that could be useful. Sent from Android On Jan 31, 2012 11:41 AM, Herby Vojčík he...@mailbox.sk wrote: Erik Arvidsson wrote: I agree with Brendan, I think we can get rid of the initializer even for for-in with var. The only code I've seen that uses this is in ES test suites. Just an idea. Google crawls over the whole (crawler-accessible) web. That means it is indexing a _lot_ of .js files out there. I saw questions like the one above appear more times on this list. How often is [insert-your-own-quirk] used / is it used ever? Code files are of defined structure, unlike people-written documents. If Google would be able to index code files with some added information that allows to search in that structure, it could yield important information about usage patterns of this or that programming language. Herby ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss -- sam th sa...@ccs.neu.edu ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Google indexing code patterns (Re: lexical for-in/for-of loose end)
On Jan 31, 2012 3:38 PM, Sam Tobin-Hochstadt sa...@ccs.neu.edu wrote: Google Code Search is no longer available, sadly. Oh, I didn't know it was shutdown. Hopefully some Google internals can still use that data to give some stats when those questions arise... ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Jan 30, 2012, at 6:38 PM, Brendan Eich wrote: No! We have a standing agreement to get rid of the awful, lazy-grammar-reuse error in ES1 that allows an initializer in for(var x=i in o). We do not want this at all for 'let' in either for-in or for-of. Fine by me, WRT let/const We haven't done a very good job of capturing such agreements. I don't think this one is reflected on the wiki proposals... I think we should break unconditionally and forbid =i in for(var x=i in o) too, but that is a separate issue. This botch in grammar factoring is a (bad) sunk cost that has zero bearing on the fresh let binding per iteration idea. It's terrible anti-precedent. Just say no. While I think the language would be better without it. I don't really see how we can justify such a breaking change. Heck, I can even think of a plausible use: function hasEnumerableProperties (obj) { for (var key = false in obj); return key!==false } Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock mailto:al...@wirfs-brock.com January 31, 2012 8:28 AM On Jan 30, 2012, at 6:38 PM, Brendan Eich wrote: No! We have a standing agreement to get rid of the awful, lazy-grammar-reuse error in ES1 that allows an initializer in for(var x=i in o). We do not want this at all for 'let' in either for-in or for-of. Fine by me, WRT let/const We haven't done a very good job of capturing such agreements. I don't think this one is reflected on the wiki proposals... You're right, I thought I'd recorded it. Sorry, fixed: http://wiki.ecmascript.org/doku.php?id=harmony:iterators#syntax_issues I think we should break unconditionally and forbid =i in for(var x=i in o) too, but that is a separate issue. This botch in grammar factoring is a (bad) sunk cost that has zero bearing on the fresh let binding per iteration idea. It's terrible anti-precedent. Just say no. While I think the language would be better without it. I don't really see how we can justify such a breaking change. Heck, I can even think of a plausible use: function hasEnumerableProperties (obj) { for (var key = false in obj); return key!==false } Yes, unlike 'let' the binding persists after the loop. We can live with this, my hope was that we could also make it an early error and survive because as Arv suggests, it's probably used only in testsuites. But you never know, and the Web tends to tile all executable surfaces and test all features. Let's see how easy it is to split the 'var' and 'let/const' cases... But you could make a twisted argument for an effect-ful 'let' initialization expression also being useful in this way. We mustn't go there. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Jan 31, 2012, at 8:36 AM, Brendan Eich wrote: Let's see how easy it is to split the 'var' and 'let/const' cases... already done But you could make a twisted argument for an effect-ful 'let' initialization expression also being useful in this way. We mustn't go there. might be useful, but it would break the scoping rules. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock mailto:al...@wirfs-brock.com January 31, 2012 8:43 AM On Jan 31, 2012, at 8:36 AM, Brendan Eich wrote: Let's see how easy it is to split the 'var' and 'let/const' cases... already done In the latest draft on the wiki? I thought I had downloaded that already. But you could make a twisted argument for an effect-ful 'let' initialization expression also being useful in this way. We mustn't go there. might be useful, but it would break the scoping rules. No, I meant this: let i = 42, j = 3; for (let x = i *= j in {}); No iterations, x not in scope after -- but i is 126 after. Do Not Want (the initialiser). /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Jan 31, 2012, at 10:09 AM, Brendan Eich wrote: Allen Wirfs-Brock mailto:al...@wirfs-brock.com January 31, 2012 8:43 AM On Jan 31, 2012, at 8:36 AM, Brendan Eich wrote: Let's see how easy it is to split the 'var' and 'let/const' cases... already done In the latest draft on the wiki? I thought I had downloaded that already. But you could make a twisted argument for an effect-ful 'let' initialization expression also being useful in this way. We mustn't go there. might be useful, but it would break the scoping rules. No, I meant this: let i = 42, j = 3; for (let x = i *= j in {}); No iterations, x not in scope after -- but i is 126 after. Do Not Want (the initialiser). for the same effec: let i = 42, j = 3; for (let x in (i *= j ,{})); Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: No, I meant this: let i = 42, j = 3; for (let x = i *= j in {}); No iterations, x not in scope after -- but i is 126 after. Do Not Want (the initialiser). for the same effec: let i = 42, j = 3; for (let x in (i *= j ,{})); So? I wrote effect not scope, now you're defending the unwanted degree of side-effecting freedom? :-|. One can always make expressions have effects. That's not the point. The reuse of VariableDeclarationNoIn in 12.6.4 without any refactoring or semantic restriction to forbid an initialiser was a mistake. I'm glad to get rid of it, but teasing me will cause endless grumpy fear that it will live on. :-P /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Jan 31, 2012, at 10:25 AM, Brendan Eich wrote: Allen Wirfs-Brock wrote: No, I meant this: let i = 42, j = 3; for (let x = i *= j in {}); No iterations, x not in scope after -- but i is 126 after. Do Not Want (the initialiser). for the same effec: let i = 42, j = 3; for (let x in (i *= j ,{})); So? I wrote effect not scope, now you're defending the unwanted degree of side-effecting freedom? :-|. One can always make expressions have effects. That's not the point. The reuse of VariableDeclarationNoIn in 12.6.4 without any refactoring or semantic restriction to forbid an initialiser was a mistake. I'm glad to get rid of it, but teasing me will cause endless grumpy fear that it will live on. :-P Oh, I'm perfectly happy to see the initializer eliminated (for the new syntax). But side-effects eradication, in general, seems like a wack-a-mole effort. Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Brendan Eich mailto:bren...@mozilla.org January 31, 2012 10:25 AM So? I wrote effect not scope, now you're defending the unwanted degree of side-effecting freedom? :-|. One can always make expressions have effects. That's not the point. The reuse of VariableDeclarationNoIn in 12.6.4 without any refactoring or semantic restriction to forbid an initialiser was a mistake. I'm glad to get rid of it, but teasing me will cause endless grumpy fear that it will live on. :-P So one approach, which Gavin has already tried in WebKit nightlies by reserving 'let' unconditionally (so far so good), is to risk breaking the web. How about we remove the optional initialiser hiding in for (var ... in ...); and see what happens? We may learn quickly that the web depends on this unwanted feature. That would be good, it would mean we must restrict 'let' and 'const' in for-in and for-of heads from having initialisers. But if we get away with it, over this year in nightlies and product builds, then I think we should remove the initialiser from the 'var' case too. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
Allen Wirfs-Brock wrote: Oh, I'm perfectly happy to see the initializer eliminated (for the new syntax). See followup. If we can try to reserve 'let' in non-strict code, we can try to remove =i in for (var x=i in o). Why not make the attempt? But side-effects eradication, in general, seems like a wack-a-mole effort. Clearly I've miscommunicated. I wasn't arguing against effects, only against the extra expression option in a zero-iteration for-in structure. There's no reason for it, it introduces non-trivial complexity into some implementations, and it's a source of minor mischief for code analysis. /be ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
No! We have a standing agreement to get rid of the awful, lazy-grammar-reuse error in ES1 that allows an initializer in for(var x=i in o). We do not want this at all for 'let' in either for-in or for-of. I think we should break unconditionally and forbid =i in for(var x=i in o) too, but that is a separate issue. This botch in grammar factoring is a (bad) sunk cost that has zero bearing on the fresh let binding per iteration idea. It's terrible anti-precedent. Just say no. /be Allen Wirfs-Brock mailto:al...@wirfs-brock.com January 30, 2012 6:09 PM Here is valid ES6 for-in statement: for (let p=alert(initializing p+p) in [0,1]) alert(p); Each iteration gets a fresh p, but does it produce three alerts saying: Initializing p 0 1 or four alerts saying Initializing p 0 Initializing p 1 I would expect the first alternative. However, taking the first alternative, what happens for let p=outer; for (let p=alert(initializing p+p) in [0,1]) alert(p); Is the first alert: initializing outer or does it throw a reference error for a temporal dead zone violation? I would expect the reference error. Essentially the for-in is conceptually wrapped with a block that has an uninitialized binding for p. The initializer of the let declaration is evaluated once in the scope of that block and then the value is discarded because it is inaccessible. For each iterations, a fresh block is created containing a binding for p that is initialized to the current iteration key and then the statement is evaluated in the scope of that block. Thoughts? Allen ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: lexical for-in/for-of loose end
On Mon, Jan 30, 2012 at 18:38, Brendan Eich bren...@mozilla.org wrote: I think we should break unconditionally and forbid =i in for(var x=i in o) too, but that is a separate issue. This botch in grammar factoring is a (bad) sunk cost that has zero bearing on the fresh let binding per iteration idea. It's terrible anti-precedent. Just say no. +1 No initializer in for-in with let. I agree with Brendan, I think we can get rid of the initializer even for for-in with var. The only code I've seen that uses this is in ES test suites. -- erik ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss