Re: Proposal: opt-out local scoping
Yes, Dave Herman already mentioned that, and I replied with this: While this is true, this is far less of a problem than opt-in local scoping, because the errors with opt-out local scoping are always going to be local to the block/function the variable was assigned in. Because of this, I believe the convenience is worth the cost. Waldemar Horwat wrote: With this proposal you just get the dual problem: You think you're assigning to an outer-scope variable while in fact you're creating and later discarding a local variable. This would just flip the confusion you worry about to the other case without eliminating it. ES proposals already provide a good solution to this problem: strict mode. In strict mode you can't accidentally create a global just because you have a missing var. You get an error if you try. Waldemar ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Code blocks (was Re: Proposal: opt-out local scoping)
Mark S. Miller wrote: Since ES is not an expression language, it's useful to divide the question into * TC for expressions expr equiv (function(){return expr;})() ES violations: this, arguments. 'this' breakage can possibly be addressed with '.bind(this)' as in my previous message. expr equiv (function(){return expr;}).call(this) * TC for blocks {stats} equiv (function(){stats})(); Same issues re this, arguments Additional ES violations: return, break, continue, var hoisting Note: The above litany of problems are not fixable in ES. But we should try to avoid digging this hole any deeper. Any procedural language will have issues with TC then. The only longterm solution to this that I can think of is to add Ruby-like code blocks to the language, which inherit this, arguments, var scope, and return/break/continue continuations. Not ever having dealt with implementing continuations, I have no idea how simple it would be to add code block semantics to the language. There are two styles to code blocks that I know of, which are not mutually exclusive: 1) adding a new type of function 2) denoting that a parameter to a function is a block, and treating any function passed in as that parameter to be a block In either style, the function artifacts (this, arguments, etc.) would be determined lexically. The first style would require some new syntax to define blocks. Redefining |this| via function.call and function.apply should also be disallowed. We have several options to choose from. Some examples: Ruby-like: do(...) { ... } do { ... } Usage of Java keywords: function(...) inherits { ... } Some obscure yet convenient syntax: #(...) { ... } The second style would require some sort of block annotation to a parameter, e.g. function(x: block} { ... } function(block x) { ... } function(x) { ... } One advantage of the second style is that we can add new types of control abstractions relatively seamlessly with some more Ruby-like sugar. If the last parameter to a function is annotated to be a block, then the following: function control(..., block f) { ... } control(...) statement would pass in a 0-parameter code block composed of |statement| (which could be a {...} block) to the function |control| as the parameter f. Example: function read(openArg, block f) { try { this.open(openArg); f(); } finally { this.close(); } } FileInputStream.prototype.read = read; function findInFiles(files, x) { for each (let file in files) { input = new FileInputStream(); input.read(file) { let lineNum = 0; for (let line in input.readLines()) { if (line == x) return [file, lineNum]; lineNum++; } } } } Hope that all made sense. -Yuh-Ruey Chen ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Proposal: opt-out local scoping
Well we're all discussing radical new syntaxes (I'm looking at you, Ingvar), I might as well propose my own. One thing about JavaScript that has always really bothered me (and probably several of you) is how JavaScript has opt-in local scope, rather than opt-out local scope. That is, in order to have an identifier reference a local variable, there needs to be a |var| declaration that says that that identifier refers to a local variable, and not non-local one (either in a surrounding function or the global scope). This has a couple related problems: 1) Lack of integrity - in that it's easy to shoot yourself in the foot. One missing |var| and you can be debugging some random error in the wrong place. 2) It's inconvenient. This is pretty self-explanatory. It also encourages the usage of redundant var declarations in often-copied/pasted code, even if it may not be necessary due to var hoisting, which is apparently a practice some people here are not fond of. 3) It can be confusing due to var hoisting. Suppose you have code like this: blah = 10; (function() { print(blah); var blah = 20; print(blah); })(); The novice user would expect this to print 10 followed by 20, when it really prints out undefined followed by 20. This has all been discussed to death before, I'm sure, with the conclusion that changing the behavior is backwards incompatible. And I vaguely remember someone saying that we shouldn't add a pragma to fix this issue, although the reason for that escapes me at the moment. So here's the proposal that is backwards compatible: provide a block that changes the default scoping. var { ... } where everything assigned in ... results in a variable local to that block. Note that this only affects _assignment_. You can still read from a variable from a surrounding scope. As some extra sugar, function name(args) var { ... } would desugar function name(args) { var { ... } } The default scoping setting also nests, i.e. it also crosses function boundaries, so in var { function name(args) { ... } } everything assigned in ... results in a variable local to the functions block. To escape (opt out) of the scoping setting, we need a new keyword. Python 2.5 uses the keyword |nonlocal| for this purpose, so I'll use it as well as a placeholder. var { ... nonlocal x [ = y]; ... } Finally, we can do the same thing for |let|: let { ... } etc. Some examples: --- function list(iterable) { var { if (iterable is Array) { lst = iterable; } else { list = []; for (x in iterable) { lst.push(x); } } } return lst; } function stats(iterable) var { // -- notice var lst = list(iterable); function calcSum() { sum = 0; for each (x in lst) { sum += x; } } sum = calcSum(); var mean; function calcMean() { nonlocal mean; mean = sum / lst.length; } return [lst.length, sum, mean]; } --- which desugars to: --- function list(iterable) { var lst; if (iterable is Array) { lst = iterable; } else { list = []; for (var x in iterable) { lst.push(x); } } return lst; } function stats(iterable) { var lst = list(iterable); function calcSum() { var sum = 0; for each (var x in lst) { sum += x; } } var sum = calcSum(); var mean = sum / lst.length; return [lst.length, sum, mean]; } --- And finally, it would be nice to have a pragma that can do this for us (again, I don't recall the argument against them). Something like: use scope var; use scope let; use scope nonlocal; // default for backwards compatibility which would obviate the need to add all these |var { ... }| and |let { ... }| statements in new code. For example, the following would be equivalent to the above examples: --- use scope var; function list(iterable) { if (iterable is Array) { lst = iterable; } else { list = []; for (x in iterable) { lst.push(x); } } return lst; } function stats(iterable) { lst = list(iterable); function calcSum() { sum = 0; for each (x in lst) { sum += x; } } sum = calcSum(); var mean; function calcMean() { use scope nonlocal; // notice that these pragmas can be local to blocks mean = sum / lst.length; } return [lst.length, sum, mean]; } --- I should also point out that this is not a radical new concept. Both Ruby and Python have had this functionality for a while. Comments? -Yuh-Ruey Chen ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
I agree with you 100% that JavaScript /almost/ gets lexical scope right and that we should try to eliminate that almost as much as we can. I don't, however, believe that the right solution is Python's implicit binding semantics. where everything assigned in ... results in a variable local to that block. Note that this only affects _assignment_. You can still read from a variable from a surrounding scope. I think you're still going to have the same subtleties as hoisting with this proposal, since all the variables assigned to in the block will be in scope for the whole block; so they'll be `undefined' before being assigned to and there will still be the same closure hazards where you might think the variable was in scope in a nested block but in fact it goes out to the nearest enclosing var { ... } block. var { ... nonlocal x [ = y]; ... } For my tastes, this puts too much of a tax on referring to outer variables. function list(iterable) { var { if (iterable is Array) { lst = iterable; } else { list = []; I think you mean `lst = []' here? for (x in iterable) { lst.push(x); } } } return lst; } These are the kinds of bugs that I think this semantics would result in: when assignment implicitly binds variables, fat-fingering an assigned variable name silently works and then causes unpredictable behavior later. Whereas with traditional lexical scope, when you declare your variables up front, both assignments and references are checked against the variables in scope, and you get an immediate error -- even statically detectable. I don't believe that var { ... a = f() ... b = g() ... c = h() ... } is that much less of a hardship than { ... var a = f() ... var b = g() ... var c = h() ... } and I believe the benefits of a clearer semantics -- and clearer and earlier errors on assignment to free variables -- are worth it. But I agree that we need to do something to correct the semantics of looking up free variables dynamically. And finally, it would be nice to have a pragma that can do this for us (again, I don't recall the argument against them). Something like: use scope var; use scope let; use scope nonlocal; // default for backwards compatibility Lexical scope is in the air. :) Please take a look at the lexical scope proposal on the wiki and offer any comments or suggestions: http://wiki.ecmascript.org/doku.php?id=strawman:lexical_scope Essentially, the above is a less radical proposal that simply uses the lexical scope that's already there in JavaScript, but as you suggest enforces it with a pragma. The result is a language where free variables, both in assignments and references, are a statically detectable error, but with otherwise essentially the same semantics that JavaScript already has. Dave ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On 2008-08-28, at 07:52EDT, Dave Herman wrote: Lexical scope is in the air. :) Please take a look at the lexical scope proposal on the wiki and offer any comments or suggestions: http://wiki.ecmascript.org/doku.php?id=strawman:lexical_scope Essentially, the above is a less radical proposal that simply uses the lexical scope that's already there in JavaScript, but as you suggest enforces it with a pragma. The result is a language where free variables, both in assignments and references, are a statically detectable error, but with otherwise essentially the same semantics that JavaScript already has. I like this, but wouldn't you want to provide escapes, like reformed with and/or a way to declare an individual reference to be free? ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
I like this, but wouldn't you want to provide escapes, like reformed with and/or a way to declare an individual reference to be free? Reformed `with' depended on type annotations and structural type syntax, which are undergoing discussion. So I think reformed `with' is up in the air for now. As for free references, what can you do with a free variable? If you mean you want a way to look something up in the global object, then use `this.id' or `this[expr]' (or `let global = this' followed by global.id/global[expr]). It might be nice to have a standard library (called `global' or something) that's bound to the global object so you can have a less fragile binding to the global object than `this'. Dave ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On 2008-08-28, at 09:09EDT, Dave Herman wrote: As for free references, what can you do with a free variable? If you mean you want a way to look something up in the global object, then use `this.id' or `this[expr]' (or `let global = this' followed by global.id/global[expr]). It might be nice to have a standard library (called `global' or something) that's bound to the global object so you can have a less fragile binding to the global object than `this'. Exactly. I think it would be worthwhile to have a standard way to refer to the global object. ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
Dave Herman wrote: I think you're still going to have the same subtleties as hoisting with this proposal, since all the variables assigned to in the block will be in scope for the whole block; so they'll be `undefined' before being assigned to and there will still be the same closure hazards where you might think the variable was in scope in a nested block but in fact it goes out to the nearest enclosing var { ... } block. Actually, I forgot to mention that in there should no longer be any hoisting in this case; it should be a syntax error: blah = 10; var { print(blah); blah = 20; print(blah); } The first |print(blah)| should be a syntax error. I think this is what Python does too. var { ... nonlocal x [ = y]; ... } For my tastes, this puts too much of a tax on referring to outer variables. The alternative, which Pythonites have used for a while until the advent of |nonlocal|, was to have an object/array in the outer scope and have the outer variable be a property of that object: var { function outer() { x = 10; a = [x]; function inner() { return a[0]; } return inner(); } } However, this is pretty ugly and isn't very discoverable. These are the kinds of bugs that I think this semantics would result in: when assignment implicitly binds variables, fat-fingering an assigned variable name silently works and then causes unpredictable behavior later. Whereas with traditional lexical scope, when you declare your variables up front, both assignments and references are checked against the variables in scope, and you get an immediate error -- even statically detectable. While this is true, this is far less of a problem than opt-in local scoping, because the errors with opt-out local scoping are always going to be local to the block/function the variable was assigned in. Because of this, I believe the convenience is worth the cost. I don't believe that var { ... a = f() ... b = g() ... c = h() ... } is that much less of a hardship than { ... var a = f() ... var b = g() ... var c = h() ... } and I believe the benefits of a clearer semantics -- and clearer and earlier errors on assignment to free variables -- are worth it. But I agree that we need to do something to correct the semantics of looking up free variables dynamically. I don't particularly like |var { ... }| myself, but it's the only other way besides a pragma - I would much prefer a pragma, which would pretty much eliminate the usage of |var|. And finally, it would be nice to have a pragma that can do this for us (again, I don't recall the argument against them). Something like: use scope var; use scope let; use scope nonlocal; // default for backwards compatibility Lexical scope is in the air. :) Please take a look at the lexical scope proposal on the wiki and offer any comments or suggestions: http://wiki.ecmascript.org/doku.php?id=strawman:lexical_scope Essentially, the above is a less radical proposal that simply uses the lexical scope that's already there in JavaScript, but as you suggest enforces it with a pragma. The result is a language where free variables, both in assignments and references, are a statically detectable error, but with otherwise essentially the same semantics that JavaScript already has. Dave I've taken a look at that, and while it does address free variables, I'd still rather not have to sprinkle redundant |var|s everywhere. ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
For what value of global should the global keyword be global? };- I'm not sure if this is what Erik meant, but my intention was just that there's a variable bound in the standard library called `global' that's bound to the global object. The only reason I suggested the name `global' was that the spec uses the terminology the global object. But there's nothing special about the variable; it can be shadowed like any other variable. I interpreted Erik's point to be that the binding of `this' is not lexically scoped, so it would be useful to have a lexically scoped variable initially bound to the global object. IOW, if I write: this.print(blah blah blah) and then I refactor the code to say: (function() { this.print(blah blah blah) })() it breaks. By contrast if I have a standard library binding `global' that's bound to the same thing as `this' at the top level, then I can write: global.print(blah blah blah) and the same refactoring: (function() { global.print(blah blah blah) })() continues to work the same. But there's no need for a special keyword or anything like that. Dave ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On Thu, Aug 28, 2008 at 12:14 PM, Dave Herman [EMAIL PROTECTED] wrote: For what value of global should the global keyword be global? };- I interpreted Erik's point to be that the binding of `this' is not lexically scoped, so it would be useful to have a lexically scoped variable initially bound to the global object. IOW, if I write: global.print(blah blah blah) and the same refactoring: (function() { global.print(blah blah blah) })() continues to work the same. Cool. Would there be a 'global' for each module (for some interpretation of module but assuming each module has its own separate top-level lexical scope, as appears to be the growing concensus)? But there's no need for a special keyword or anything like that. Would that really satisfy Erik's use case? He seemed to think that doing, at the top level -- var global = this; function foo() { global.bar = 3; } is vulnerable to some ${person} going -- function foo() { var global = /* something else */ global.bar = 3; /* now not the *real* global; system fails! */ } ? Ihab -- Ihab A.B. Awad, Palo Alto, CA ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On Aug 28, 2008, at 12:14 PM, Dave Herman wrote: I interpreted Erik's point to be that the binding of `this' is not lexically scoped, so it would be useful to have a lexically scoped variable initially bound to the global object. IOW, if I write: this.print(blah blah blah) and then I refactor the code to say: (function() { this.print(blah blah blah) })() it breaks. It happens not to break, because ES3 requires a null |this| for such calls, where the null is later replaced by the global object. obj = { method: function () { this.print(blah blah blah); }, print: function () { print(not the print you want); } }; obj.method(); would break, though. We've been trying to fix the ES3 null-global rule for a while. Any change is an incompatible change, but the current rule leads to unintended global mutation and capture bugs. By contrast if I have a standard library binding `global' that's bound to the same thing as `this' at the top level, then I can write: global.print(blah blah blah) and the same refactoring: (function() { global.print(blah blah blah) })() continues to work the same. But there's no need for a special keyword or anything like that. Indeed, Doug Crockford proposed at the January TC39 meeting this year to make 'this' act like a lexically bound variable, with the only magic to it applying to the case of obj.method() call expressions (and variatons, obj[name] where name = 'method'), where 'this' would be overridden -- if you will, a shadowing 'this' would be bound to obj. I liked Doug's proposal quite a bit. I do not see anything like it in ES3.1, but I'd like to see it in Harmony. /be ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On Aug 28, 2008, at 1:23 PM, P T Withington wrote: I'd like to have a syntax where this is _not_ implicitly bound. One idea would be to riff on default arguments: function example (receiver=this, ... Why wouldn't you use |this| for the parameter name? We did for type- annotating |this| in various proposals. Some functions have a bound |this| when you extract them. Such override attempts would fail. Throw or fail silently? /be ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On Thu, Aug 28, 2008 at 12:29, Dave Herman [EMAIL PROTECTED] wrote: Would that really satisfy Erik's use case? He seemed to think that doing, at the top level -- var global = this; function foo() { global.bar = 3; } is vulnerable to some ${person} going -- function foo() { var global = /* something else */ global.bar = 3; /* now not the *real* global; system fails! */ } You can make the same case for any variable. This is just lexical scope in action. The meaning of a variable reference is its innermost lexical binding. If you shadow a variable, it's shadowed. Erik, was this your concern? I'm not concerned about someone shadowing it. My concern was with cases like the one Brendan pointed out as well as code snippets like: new function() { var global = this; global.print(124); } Some people tend to use that format over (function() { })() and wrapping code with that construct would break the global reference. -- erik ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
Dave Herman wrote: Reformed `with' depended on type annotations and structural type syntax, which are undergoing discussion. So I think reformed `with' is up in the air for now. I find it odd that reformed |with| required such exact type annotations, when nothing else does. It would seem that these are very similar, from an early-binding viewpoint: var {a, b, c} = fn(); x = a; var obj = fn(); x = obj.a; with (obj : {a, b, c}) { x = a; // Desugars to obj.a } Equally it would seem that disambiguating syntax would allow early binding, if it were enforced: with (obj) { x = .a; // Desugars to obj.a y = a; // From the lexical scope } -- Ingvar von Schoultz ___ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Proposal: opt-out local scoping
On Aug 28, 2008, at 2:59 PM, Ingvar von Schoultz wrote: Dave Herman wrote: Reformed `with' depended on type annotations and structural type syntax, which are undergoing discussion. So I think reformed `with' is up in the air for now. I find it odd that reformed |with| required such exact type annotations, when nothing else does. Please read http://wiki.ecmascript.org/doku.php?id=proposals:reformed_with the last paragraph in particular. It would seem that these are very similar, from an early-binding viewpoint: var {a, b, c} = fn(); x = a; var obj = fn(); x = obj.a; with (obj : {a, b, c}) { x = a; // Desugars to obj.a } Your example includes neither type constraints nor mutation. Equally it would seem that disambiguating syntax would allow early binding, if it were enforced: with (obj) { x = .a; // Desugars to obj.a New syntax could solve the ambiguity problem, but not the type variance problem. /be y = a; // From the lexical scope } -- Ingvar von Schoultz ___ 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