`static` keyword from C/C++ as own closured var declaration
Allen suggested something like this in the September meeting. One issue people had with it was that it adds another violation of the equivalence between a statement <stmt> and (function()<stmt>)(), which is a refactoring hazard. Put differently, if you have some code with "static" declarations in it, you can't wrap the code in a function body without breaking the code, which makes it brittle.
Separate from that, I also don't really see how the idea really buys you all that much. The analogy to C is only so helpful, since C doesn't have nested functions (and static therefore always lifts to global scope), so mostly it just reads to me like a rather obscure way of saying "oops, I meant to bind this over there."
On 23.11.2010 12:07, David Herman wrote:
Allen suggested something like this in the September meeting. One issue people had with it was that it adds another violation of the equivalence between a statement<stmt> and (function()<stmt>)(), which is a refactoring hazard. Put differently, if you have some code with "static" declarations in it, you can't wrap the code in a function body without breaking the code, which makes it brittle.
Can you give a small example (it's just interesting) -- to see the issue?
Separate from that, I also don't really see how the idea really buys you all that much. The analogy to C is only so helpful, since C doesn't have nested functions (and static therefore always lifts to global scope), so mostly it just reads to me like a rather obscure way of saying "oops, I meant to bind this over there."
It seems to me too that such a way may bring a confusion to JS development. And I also agree that in C it was caused by the issue of absence nested functions with lexical scope.
Dmitry.
David Herman:
// "new" more readable sugar function() { static x = 1; // hello c/c++
return ++x; }
This would produce incompatibilities with ECMAScript 5 non-strict
code. The word static
can be used as an Identifier in ES5
non-strict mode.
On Tue, Nov 23, 2010 at 2:12 AM, Asen Bozhilov <asen.bozhilov at gmail.com>wrote:
David Herman:
// "new" more readable sugar function() { static x = 1; // hello c/c++
return ++x; }
This would produce incompatibilities with ECMAScript 5 non-strict code. The word
static
can be used as an Identifier in ES5 non-strict mode.
Any ES5 non-strict code that uses "static" as an identifier is already incompat with ES5/strict, so this is not an issue. The case against "static" seems strong, but this does make it stronger.
Can you give a small example (it's just interesting) -- to see the issue?
Sure thing. Say you're writing some code with a constant value, and somewhere inside the code you use `static':
var METERS_PER_SQUARE_KILOJOULE = 17.4;
...
static foo = 1;
...
f(foo, METERS_PER_SQUARE_KILOJOULE);
Now you decide you want to parameterize over the constant, instead of a fixed constant:
function(metersPerSquareKiloJoule) {
...
static foo = 1;
...
f(foo, metersPerSquareKiloJoule);
}
This change accidentally alters the scope of `foo'.
How is your example any different from if you had said: const foo=1;
In both cases, wrapping the declaration with a function changes its scope??
Allen
-----Original Message---
Hm, that's an interesting point: all declaration forms are sensitive to being wrapped in a function, e.g.:
|(function() { var x })()| != |var x|
That pretty much nixes that critique!
-----Original Message---
On 11/22/10 20:18, Bga wrote:
// es3 way (function() { var x = 1;
return function() { return ++x; } })();
// current es6/SM1.8 way let(x = 1) function() { return ++x; }
// "new" more readable sugar function() { static x = 1; // hello c/c++
return ++x; }
Implementation, when compiling source code, just collects 'static' vars from scope and wraps current scope to closure scope with collected vars
This was already discussed at the September meeting. Some interesting issues are:
-
How far does "static" hoist? One let-scope? One block-scope? One function-scope? One class-scope?
-
Refactoring by adding or removing a scope becomes quite error-prone.
-
To avoid breaking refactoring, you'd need to allow "static static", "static static static", etc.
-
In what scope do you look up the initializer expressions? What if they reference variables that were shadowed in between the scope where they're actually present and the one into which they got hoisted?
-
When do the initializers run? What if it's a static const?
-
What happens if you have two of the functions above, each trying to hoist its own "x" into the same scope? Under which conditions do the x's stay separate, and under which ones do they become the same variable?
Waldemar
Waldemar has good questions, most of which would be addressed in a full proposal. But some off the cuff answers are embedded below Allen -----Original Message---
On 11/23/10 15:18, Allen Wirfs-Brock wrote:
How far does "static" hoist? One let-scope? One block-scope? One function-scope? One class-scope? AWB: the simple answer would be one block-scope (isn't that the same as a let-scope??). It probably will take some experimentation to verify that the simple solution has reasonable use cases. If not, function scope may be more appropriate. Until and unless we have class declarations there really isn't anyway to consider class-scopes.
Refactoring by adding or removing a scope becomes quite error-prone. AWB: Just as it is for function and var declarations. Function scoping static might actually alleviate refactoring issues.
It's substantially worse here. If we adopt block-scope, we get:
function f(b) { if (b) return 43; static x = 1; return x++; }
If we adopt the block-scope recommendation, refactoring it to:
function f(b) { if (b) { return 43; } else { static x = 1; return x++; } }
will do something completely different.
- To avoid breaking refactoring, you'd need to allow "static static", "static static static", etc. AWB: ?? examples?? BTW, isn't it the job of refactoring tools to address issues like this. We shouldn't assume that JavaScript programmers will always be restricted to dump editors.
See example above. The local way to fix the refactoring is to turn it into:
function f(b) { if (b) { return 43; } else { static static x = 1; return x++; } }
The same issue arises if we make a different decision on how far "static" hoists, but the examples are slightly bigger.
In what scope do you look up the initializer expressions? What if they reference variables that were shadowed in between the scope where they're actually present and the one into which they got hoisted? AWB: The scope surrounding the construct they are hoisted out of (either block or function depending upon the first design decision). If that sort of shadowing is actually possible, it should be an early error if it actually occurs. Also need to consider whether initializer expressions can reference other statics in the same scope.
When do the initializers run? What if it's a static const? AWB: Before entering the scope it is hoisted out of. For function scope this would be specified as part of "variable instantiation". static const behavior has same scope-hole rules as regular const.
I asked about static const because now you have the new problem of multiple entry into the scope that does the const initialization.
- What happens if you have two of the functions above, each trying to hoist its own "x" into the same scope? Under which conditions do the x's stay separate, and under which ones do they become the same variable? AWB: They are always separate. Each function gets its own static scope that exists between the function and its enclosing scope. This is illustrated in Bga's "ES3 way" desugaring.
So what should the following examples do?
A. function f() { x = 3; static x = 2; return x; }
B. function f() { let x = 3; static x = 2; return x; }
C. function f() { static x = 3; static x = 2; return x; }
D. function f(b) { if (b) { static x = 1; return x++; } else { static x = 1; // Same x or different? return x++; } }
Waldemar
Thanks for the test examples. They point out that there are situations that need to be dealt with that don't naturally arise if a programmer writes code using the basic desugaring previous suggested. However, I don't seen anything that is insurmountable in terms of defining the semantics. It also points out that in general we may not get away with just saying syntax x desugars into form y. You have to consider what static semantic expectations that the use of syntax x is likely to create for the programmer and deal with those expectations.
More specific responses marked with AWB below.
Allen
-----Original Message---
The location of a var establishes both the lexical scope where the variable is visible (the enclosing function) and the dynamic scope within which all uses of it refer to the same binding (each activation of that function). "let" has different scoping rules, but the same relationship between scope of visibility (the enclosing block) and scope of sharing (each activation of that block).
What about "static"? We want a static variable's visibility to be lexically scoped. We just want to vary the scope of sharing.
If "static" means "shared per-Function-object" or "shared per activation of the enclosing scope of the static's scope", we quickly run into weird behavior. Another example, on top of Waldemar's:
function foo() { function nextID() { static s = 0; return s++; } ... ... }
Imagine what a programmer who wants nextID to produce unique ids would think upon discovering that it doesn't. What could the bug possibly be? Moving nextID out to global scope fixes it. Think how crazy this is. The function doesn't even refer to anything in its scope-- yet its scope affects its behavior?
So I think the only sane semantics for "static" would be "a single variable shared by all activations". If there is but a single "s", the above confusion never arises. I wonder, though, if there isn't some other equally puzzling situation, its dual.
// es3 way (function() { var x = 1;
return function() { return ++x; } })();
// current es6/SM1.8 way let(x = 1) function() { return ++x; }
// "new" more readable sugar function() { static x = 1; // hello c/c++
return ++x; }
Implementation, when compiling source code, just collects 'static' vars from scope and wraps current scope to closure scope with collected vars