Class literals: does "public" still make sense?
This was one of the options we considered. At one point it was the main class proposal. The problem is that since it looks exactly like an imperative assignment to a property of "this", it should have the semantics of an imperative assignment to a property of "this". In fact, the current class proposal does not disallow it, and gives it exactly these semantics.
However, when used as the only or main means of initializing an instance, it defeats one of the main purpose of classes: The shape of the instances of a given class are no longer a static declarative feature of the class itself. Again, the contrast with modules is instructive. A CommonJS module exports only by imperative assignment to properties of an "exports" object. A proposed ES-next module exports by annotating top level declarations with "export". As an abstraction mechanism, it would be bizarre to have good abstraction properties only for abstractions that cannot be multiply instantiated.
Static analysis of the constructor should be able to do most of what "public" does, but after consulting the spec again, I see its advantages:
- Const classes make public properties unconfigurable and read-only.
- Shouldn’t public properties in non-const classes be unconfigurable, too? Are such properties expected to be configured? Read-only properties could be created via "public const".
On Sep 24, 2011, at 7:09 PM, Mark S. Miller wrote:
However, when used as the only or main means of initializing an instance, it defeats one of the main purpose of classes: The shape of the instances of a given class are no longer a static declarative feature of the class itself.
Hi Mark, a minor nit-pick: static meaning for all time from construction on applies only for 'const class'. For just 'class', there's no such shape guarantee.
Again, the contrast with modules is instructive. A CommonJS module exports only by imperative assignment to properties of an "exports" object. A proposed ES-next module exports by annotating top level declarations with "export".
Modules are not objects unless you ask for their reflections, and the reflecting module object is sealed. This is in contrast to non-const classes, the nit-pick again.
As an abstraction mechanism, it would be bizarre to have good abstraction properties only for abstractions that cannot be multiply instantiated.
Modules are instantiated in a real loaded-by-module-loader sense, but not observably as objects unless you ask for a reflection. The reflection must be sealed or the early errors for misspelled/mismatched import vs. export usage are lost, or become prone to confusion (e.g. if we allowed dynami expandos on module objects but they of course did not inject dynamic bindings into the module's scope).
These differences seem more than nits. If you have a const class, you have a factory (itself hardened by freezing) that creates sealed instances. A module is not a factory at all, and its memoized reflection is sealed.
Having written all this, I agree we want declarative syntax for class instance variables.
If the intent of classes is to provide a declarative syntax for its 'shape' then dropping the private syntax in lieu of private name objects seems to run counter to this philosophy. As I understand it, private name objects are a runtime construct dependent on the '@name' module and thus have no declarative definition. Are there yet to be defined ways to declaratively define/discover an instances private namespace?
On Sep 25, 2011, at 1:04 PM, Kam Kasravi wrote:
If the intent of classes is to provide a declarative syntax for its 'shape' then dropping the private syntax in lieu of private name objects seems to run counter to this philosophy.
We did not agree to drop the private declaration syntax at the July TC39 meeting. Perhaps my understanding of our agreement then does not match Marks?
What we agreed to drop was the private(this) straw syntax in the classes proposal, in favor of this[x], this[y], for private-declared private name objects x and y.
We did not agree to drop the private declaration syntax at the July TC39 meeting. Perhaps my understanding of our agreement then does not match Marks?
What we agreed to drop was the private(this) straw syntax in the classes proposal, in favor of this[x], this[y], for private-declared private name objects x and y.
For public properties one has two options:
- this.foo = foo;
- public foo = foo;
Using Mark’s terminology, #1 is imperative, #2 is declarative.
Private properties would be declared like this (right?) this[myPrivateName] = someData;
This is imperative (like #1), there is no way to do this declaratively (like #2).
I think it’s a clean solution and am not sure how much private properties are needed for non-security-critical applications. But it does not give you the automatic “const-ification” of “public”.
On Sun, Sep 25, 2011 at 2:08 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Sep 25, 2011, at 1:04 PM, Kam Kasravi wrote:
If the intent of classes is to provide a declarative syntax for its 'shape' then dropping the private syntax in lieu of private name objects seems to run counter to this philosophy.
We did not agree to drop the private declaration syntax at the July TC39 meeting. Perhaps my understanding of our agreement then does not match Marks?
It matches.
What we agreed to drop was the private(this) straw syntax in the classes proposal, in favor of this[x], this[y], for private-declared private name objects x and y.
We definitely agreed to drop private(this) and private(other), as these were only ever placeholders for a needed syntax anyway. My own experience trying to use them is that they are unbearably verbose. We agreed to use the this[x] syntax provided it works out, depending on the price syntax and semantics of private names. Btw, I am optimistic it will work out. I just want to keep that qualifier on the table ;).
On Sep 25, 2011, at 2:20 PM, Axel Rauschmayer wrote:
We did not agree to drop the private declaration syntax at the July TC39 meeting. Perhaps my understanding of our agreement then does not match Marks?
What we agreed to drop was the private(this) straw syntax in the classes proposal, in favor of this[x], this[y], for private-declared private name objects x and y.
For public properties one has two options:
- this.foo = foo;
- public foo = foo;
Using Mark’s terminology, #1 is imperative, #2 is declarative.
Private properties would be declared like this (right?) this[myPrivateName] = someData;
This is imperative (like #1), there is no way to do this declaratively (like #2).
At some point, we had
private myPrivateName;
in the constructor body as the way to declare a private instance variable.
It's a bit odd to allow an initializer I in such a declaration and have it mean this[myPrivateName] = I. This may be the glitch.
Even with the @ syntax that's not on the boards, a private x, y; declaration would not include the @ sigil, which seemed verbose yet inconsistent.
What's more, often the ctor takes parameters that one wants to initialize the same-named privates. This led to discussion here (and support in CoffeeScript prefiguring that discussion) of:
class Point { constructor(@x, @y) {} ... }
as short for
class Point { constructor(x, y) { private x = x, y = y; } ... }
It seems to me we'll quickly run into user push-back if we do not private any private instance variable sugar, not even for declaring and initializing from ctor parameters. But we can "go there" if it's the best way to make progress.
Would the new private syntax then go from
(old syntax) class Monster { // The contextual keyword "constructor" followed by an argument // list and a body defines the body of the class’s constructor // function. public and private declarations in the constructor // declare and initialize per-instance properties. Assignments // such as "this.foo = bar;" also set public properties. constructor(name, health) { public name = name; private health = health; } // An identifier followed by an argument list and body defines a // method. A “method” here is simply a function property on some // object. attack(target) { log('The monster attacks ' + target); } // The contextual keyword "get" followed by an identifier and // a curly body defines a getter in the same way that "get" // defines one in an object literal. get isAlive() { return private(this).health > 0; } // Likewise, "set" can be used to define setters. set health(value) { if (value < 0) { throw new Error('Health must be non-negative.') } private(this).health = value } // After a "public" modifier, // an identifier optionally followed by "=" and an expression // declares a prototype property and initializes it to the value // of that expression. public numAttacks = 0; // After a "public" modifier, // the keyword "const" followed by an identifier and an // initializer declares a constant prototype property. public const attackMessage = 'The monster hits you!'; }
to: class Monster { // The contextual keyword "constructor" followed by an argument // list and a body defines the body of the class’s constructor // function. public and private declarations in the constructor // declare and initialize per-instance properties. Assignments // such as "this.foo = bar;" also set public properties. constructor(name, healthvalue) { public name = name; module name from "@name"; private health = name.create(); this[health] = healthvalue; } // An identifier followed by an argument list and body defines a // method. A “method” here is simply a function property on some // object. attack(target) { log('The monster attacks ' + target); } // The contextual keyword "get" followed by an identifier and // a curly body defines a getter in the same way that "get" // defines one in an object literal. get isAlive() { return this[health] > 0; } // Likewise, "set" can be used to define setters. set health(value) { if (value < 0) { throw new Error('Health must be non-negative.') } this[health] = value } // After a "public" modifier, // an identifier optionally followed by "=" and an expression // declares a prototype property and initializes it to the value // of that expression. public numAttacks = 0; // After a "public" modifier, // the keyword "const" followed by an identifier and an // initializer declares a constant prototype property. public const attackMessage = 'The monster hits you!'; } ?
If so it seems odd, privately named variables like health, although declared in the constructor are implicitly available throughout the class.
Would the new private syntax then go from
(old syntax) class Monster { // The contextual keyword "constructor" followed by an argument // list and a body defines the body of the class’s constructor // function. public and private declarations in the constructor // declare and initialize per-instance properties. Assignments // such as "this.foo = bar;" also set public properties. constructor(name, health) { public name = name; private health = health; }
// An identifier followed by an argument list and body defines a // method. A “method” here is simply a function property on some // object. attack(target) { log('The monster attacks ' + target); }
// The contextual keyword "get" followed by an identifier and // a curly body defines a getter in the same way that "get" // defines one in an object literal. get isAlive() { return private(this).health > 0; }
// Likewise, "set" can be used to define setters. set health(value) { if (value < 0) { throw new Error('Health must be non-negative.') } private(this).health = value }
// After a "public" modifier, // an identifier optionally followed by "=" and an expression // declares a prototype property and initializes it to the value // of that expression. public numAttacks = 0;
// After a "public" modifier, // the keyword "const" followed by an identifier and an // initializer declares a constant prototype property. public const attackMessage = 'The monster hits you!'; }
to: class Monster { // The contextual keyword "constructor" followed by an argument // list and a body defines the body of the class’s constructor // function. public and private declarations in the constructor // declare and initialize per-instance properties. Assignments // such as "this.foo = bar;" also set public properties. constructor(name, healthvalue) { public name = name; module name from "@name"; private health = name.create(); this[health] = healthvalue; }
// An identifier followed by an argument list and body defines a // method. A “method” here is simply a function property on some // object. attack(target) { log('The monster attacks ' + target); }
// The contextual keyword "get" followed by an identifier and // a curly body defines a getter in the same way that "get" // defines one in an object literal. get isAlive() { return this[health] > 0; }
// Likewise, "set" can be used to define setters. set health(value) { if (value < 0) { throw new Error('Health must be non-negative.') } this[health] = value }
// After a "public" modifier, // an identifier optionally followed by "=" and an expression // declares a prototype property and initializes it to the value // of that expression. public numAttacks = 0;
// After a "public" modifier, // the keyword "const" followed by an identifier and an // initializer declares a constant prototype property. public const attackMessage = 'The monster hits you!'; } ?
If so it seems odd, privately named variables like health, although declared in the constructor are implicitly available throughout the class.
On Sep 25, 2011, at 3:12 PM, Kam Kasravi wrote:
class Monster { // The contextual keyword "constructor" followed by an argument // list and a body defines the body of the class’s constructor // function. public and private declarations in the constructor // declare and initialize per-instance properties. Assignments // such as "this.foo = bar;" also set public properties. constructor(name, healthvalue) { public name = name; module name from "@name"; private health = name.create();
No, that's silly. If there is to be private declaration syntax, it should generate the private name, class-private (shared among instances). What you just spelled out wants 'const' not 'private', but hoisted to an outer closure. The way you wrote it, each constructor call would make a new and unique private name, so you'd get instance-private instance variables, not class-private.
this[health] = healthvalue;
This part is what we agreed to in July.
I see, so private declarations may go back to being defined at the class level - at least semantically - in order for the private name to be in scope across the class eg this[heath] referenced in Monster prototype methods: get isAlive(), Monster.set health().
Without private members, do we still need the keyword "public" in class literals?
For example, instead of constructor(geometry, materials) { super(geometry, materials);
} I find the following just as intuitive, without the need for the keyword "public": constructor(geometry, materials) { super(geometry, materials);
}
Similarly intuitive is something that I’ve seen somewhere – passing through constructor arguments as members. class Point { constructor(this.x, this.y) { } }
I don’t think "public" helps, I think it makes things less intuitive.