proposal-class-fields
proposal-class-fields copied to clipboard
A new proposal, maybe late maybe not.
Stop the class-fields proposal! strong recommend!
There is now a new proposal, no prefix '#', no FIELD, no newly concepts! please rate it.
Maybe we still have time to stop this disaster. say no accept! say no for class-fields proposal! see here #100 !
History of new proposal named private-property:
[2019.08.22]
[2019.08.29]
Thanks all.
The new syntax note:
class MyClass {
private x = 100;
foo() {
console.log(x); // 100
}
}
for protected property:
class MyClass {
protected x = 200;
foo() {
console.log(x); // 200
}
}
class MyClassEx extends MyClass {
bar() {
console.log(x); // 100
}
}
for internal access:
class MyClass {
internal private x = new Object;
compare(b) {
console.log(x === b[internal.x]); // true
}
static compare(a, b) {
console.log(a[internal.x] === b[internal.x]); // true
}
}
What if I want a local of the same name?
This has been proposed and rejected several times. Please see the FAQ.
@bakkot The new proposal have not that problem, reason for rejecting "private" is not sufficient!
@jhpratt In current context, you know any name for class context, so next case is excessive or meaningless:
class f() {
private x;
foo(x) {
// no! you know have a private 'x', why named arguments-x ?
}
}
so, Need to be careful about protected property inhertied from parent. ex:
class f() {
protected x;
}
class f2 extends f {
foo(x) {
// i unknow has 'protected x', or know it but can not change that!
}
}
Easy!
To define alias with private as syntax:
class f3 extends f {
private x as xxx;
foo(x) {
console.log(x); // arguments x
console.log(xxx); // protected x
}
}
// more
class f4 extends f3 {
foo() {
console.log(x); // protected x from f, or f3
}
}
Nothing has ever prevented someone from creating a local with a valid identifier as a name. Why should that change?
@jhpratt
Have a private scope in context of class definitions, methods is sub-level block in the class context. when parent has 'x', sub-level block choice either override and new-name. same of:
function MyClass() {
let x = 100;
function MyMethod() {
let x = 100; // override up-level
let xx = 1000; // new name
}
}
in MyClass private scope, all identifier is valid and known, MyMethod is free either override and new-name.
Perhaps I should be clearer. If I can create a local binding (which I think you're saying you can), how would I then access the private member? That's an absolute must for any proposal.
Please provide a more thorough example and explanation.
@jhpratt
Maybe, you need a newly name for exist private name:
class MyClass {
private x = 100;
...
private x as internal_own_x;
foo(x) {
console.log(x);
console.log(internal_own_x);
} }
// OR
private get internal_own_x() { return x };
foo(x) {
console.log(x);
console.log(internal_own_x);
}
@jhpratt hahaha, i know you will 👎
give a good idea to me? if dup identifier in scope or context, we can do what?
I gave you a -1 because I don't believe you have fully thought this through, and that your proposal is not feasible. Your solution to my concern is hacked together. As I said in my previous comment, please provide a more thorough example and explanation.
This is a venue for serious discussion. If you're not willing to take my concern seriously, and are looking to complain about receiving a "thumbs down", I suggest you reconsider what you're here for.
It's price for choice of identifier or class's fields. for identifier, impact is absolute exist in context of the class's definitions, but concept simple, and easy, and short code, these are good side.
@jhpratt
no, I am discussing this issue seriously and formally. i accept any questions and things.
so, try testcases? I implement parser and interpreter base prepack and babel-parser. For syntax, I tried combined all case of private/protected/public. but, dup definitions is bound in one context. this is choice, not design.
@aimingoo how can i access the private data of another object with your proposal? One common example is static compare(a, b) { return a.#hashCode === b.#hashCode; }.
The new proposal have not that problem
Yes, it does. That FAQ entry addresses all proposals which use private x to declare fields and which do not use this.x to access them. Yours is such a proposal.
@ljharb
Thanks. a big problem, key issue. good! 👍
Have some design principles of visibility of the solution:
-
not possible to access a private member of an object when his Class unaware. so,
-
not possible to directly access
private scopeoutside of the Class declaration.
So we can only read them using protected or public method. ex:
class MyClass {
private x;
get x() {
return x;
}
set x(v) {
x = v;
}
}
Or using simple `public as` syntax to automatically add those access methods:
```javascript
class MyClass {
private x;
public as x; // same above
Based on these, have two ways for your question.
Case 1
// class design based
class InternalMyClass {
protected hashCode;
static compare(a, b) {
let getter = MyClassHelper.prototype.getHashCode;
return getter.call(a) === getter.call(b);
}
}
// access protected scope in child-class
class MyClassHelper extends InternalMyClass {
getHashCode() { // this.getHashCode()
return hashCode; // return this.#hashCode
}
}
// publish the class only
export class MyClass extends InternalMyClass {
private as hashCode; // update or not
}
NOTE: can access its private data by instance only. have not other way!
Have not public-interface to access private data of a/b, unless you design method to access them in class. so, need make a helper class to provide a interface for static method in above example.
And, MyClassHelper and MyClass are inherited from InternalMyClass, can access protected scope with same one inherited private-key.
Case 2
// or hijack skill
const ACCESS_HASHCODE = Symbol(); // any name or symbol
let internal_getter;
class MyClass {
private hashCode = 100;
[ACCESS_HASHCODE]() { // public at MyClass.prototype
return hashCode;
}
static compare(a, b) {
return internal_getter.call(a) === internal_getter.call(b);
}
}
internal_getter = MyClass.prototype[ACCESS_HASHCODE];
delete MyClass.prototype[ACCESS_HASHCODE];
NOTE: The public method send
thisto private scope, and we will got it in scope-chain when identifier resolving.
Okay, pls try test cases at here:
https://github.com/aimingoo/prepack-core/blob/proposal-private-property/test/private-property/private-access-in-class-methods.js
and here:
https://github.com/aimingoo/prepack-core/blob/proposal-protected-property/test/protected-property/classes-access-private-scope.js
You can checkout these branch and run test. @here
You didn't really answer his question. How could you do static compare(a, b) { return a.#hashCode === b.#hashCode; } without leaking it via a mandatory public field? A simple code block would suffice to explain that.
@aimingoo You're not going to get anywhere with this as along as:
- class members cannot directly and concurrently access the private properties of multiple instances of that class.
- developers don't have the freedom to add public properties of any name to the class, regardless of whether or not a private property exists with the same name.
The first one is absolute. There's no getting around it. For the second, there's a work around. It's perfectly fine for the lexical class definition to disallow duplicate property names. However, that needs to be a purely lexical limitation. Beyond the declaration, any member or caller needs to be able to add public properties to the class without restriction. Of course, as soon as you do that, you introduce the need to differentiate between private and public property accesss. Hence the various approaches that have been tossed around (.#, #./#[], ::, ->, private(obj), etc).
Dealing with that is what puts proposals against the artificial "private x implies this.x" constraint mentioned by the FAQ.
@jhpratt @rdking
Thanks, We are discussing a key issue, so please allow me to talk a little more.
Have problems at three side in for proposal class-fields:
- for concept: what fields? property or not?
- for implement: map or lex scope, or more... high cost and weight.
- for syntax: this.#x is ugly.
I have discussed these before, such as #148 . but We need a solution to these problems, specific.
Now, my proposal class-property provides a solution to problems in two areas, but not all three. Really, "access private members of instances in its invalid area" is incomplete. But this is only at the syntax/grammatical level, I will explain this below.
The class-property have a core concept: private property is property in private scope, not fields, no conceptual conflict.
For implement, class-property request a reference to access private scope in a method. the private access trigger by method only, but the reference is simple. ex: {base: env_current_method_context, name: the_keyname, thisValue: the_instance}.
And base these, class-property proposal deliver a framework/scene, to resolving third problem. so that's incomplete syntax solution using identifier to access. Currently, the class-property have not newly or better syntax to replace .# in next cases:
class MyClass {
compare(b) {
return hashCode === b.#hashCode;
}
static compare(a, b) {
return a.#hashCode === b.#hashCode;
}
}
But, internal_getter.call(a) provides a basis or evidence for a grammar discussion at above. It explains that there is such a grammatical evolution as follows:
x_internal_getter.call(b)
-> private(b).x
-> (private b).x
-> #b.x // enter private of b first, next got x
-> b#x // enter private of b, and access x
For syntax b.#x or b#x or other, need include two semantics, the enter and/next to access. In any case, the syntax return that reference {base, name, theValue} will enough in solution framework by class-property proposal.
I hope this proposal has the ability to end discussions on concepts and implementation levels, and recommend private member syntax like private x = .... But it does not include a syntax such as .# to access object instances other than <this> in method context.
NOTE: That
(private b).xmay be good. maybe... ^^.
Fields aren’t a conceptual conflict - it’s a new concept. Properties that aren’t public is also not a concept that exists. Inventing one or the other is the same thing - adding a new concept to the language.
“ugly” is subjective, and not everyone agrees with this, so it’s probably best to remove this consideration entirely unless comparing two semantically identical forms (which isn’t the case here).
Any proposal that does not allow for the static compare method i mentioned earlier is a nonstarter for me, and i suspect for other committee members too.
Fields aren’t a conceptual conflict - it’s a new concept.
Indeed, fields are a new concept, but that concept is in conflict with the existing concepts of ES that carry a similar purpose. Ignoring private fields for a moment, public fields are instance properties provided by the class. However, ES is a language for which such "provided properties" are meant to be delivered by a prototype; hence a "prototype-oriented language". Whether you accept it or not, that's a conflict.
What's worse is that this very conflict becomes even more evident in the "trade-offs" that had to be made to arrive at the class-fields proposal, and the further trade-offs that will have to be made by both developers who wish to use fields, and developers who use libraries containing classes with fields. So yes, there are indeed conceptual conflicts between the "new concept" of fields and the existing concepts in ES.
“ugly” is subjective, and not everyone agrees with this
...but that's not the reason we shouldn't bother with this argument. In support of the "ugly" claim, I've yet to find a JS developer who didn't have an immediate negative reaction to the aesthetic of this syntax. While there are those who won't have that reaction on first blush, I'd wager they're in the minority. The reason we shouldn't bother with that argument is that, within the limitations of the approach taken, # was indeed the only rational choice. The problem isn't with the choice made, but with the artificial limitations that forced it.
Any proposal that does not allow for the static compare method i mentioned earlier is a nonstarter for me, and i suspect for other committee members too.
..and even those of us who do not wish to see class-fields reach stage 4. Concurrent access to the private data of multiple instances is an absolute necessity.
However, ES is a language for which such "provided properties" are meant to be delivered by a prototype; hence a "prototype-oriented language". Whether you accept it or not, that's a conflict.
As we've discussed many times, this is objectively false; "own properties" exist; and many paradigms exist that don't use class or prototypes at all. Properties are meant to be delivered by any means necessary, which does not require a prototype whatsoever (but surely includes prototypes as one of many mechanisms). The conflicts you're referring to simply don't exist.
The conflicts you're referring to simply don't exist.
@ljharb
Conceptual conflict is absolutely existing.
The Object is defined as "object is collection of properties" in ECMAScript. So if Field is not a property, it must not belong to the concept set of "Object's member (collection elements of object)"; if Filed is property, then public field must be equal to property, there is a conceptual conflict.
This is the root of all existing contradictions.
NOTE: "an object is a collection of zero or more properties." ECMAScript Overview part.
@aimingoo and exotic objects also exist, and objects can have internal slots - they’ve never just been a collection of properties. Class fields merely expands their definition; there’s no conflict.
"own properties" exist; and many paradigms exist that don't use class or prototypes at all.
... and we don't disagree here. Never have. However, if we ignore the fact that in ES we can construct a class that is an instance of itself, a class and an instance are 2 different things. A class is a factory that produces instances via a "template", and initializes the instance via a "constructor". Since the inception of ES, the means of sharing pre-defined properties and behavior has been to create an object containing those properties and behavior, and use it as a prototype for all subsequent structures being created. That's a class, with the template being the prototype object.
There is one other long-standing approach: object factories. This approach re-creates each property and method new so that, unlike with a class, it cannot be said that 2 different instances created from a factory are of the same type. Even V8 would internally treat such instances as being of different types.
So let me throw another log on the fire and say that this proposal also conceptually conflicts with the general concept of a class. In most other languages with classes, the entire structure of the class instance is known before even the first ancestor's constructor is run. This allows for situations like subclasses overriding functions that get called by ancestors during the constructor. If this proposal were to preserve the prototypal nature of a class, then that would still be the case as the prototype is applied before the constructor code using this can be run.
Understand, I'm not trying to spark yet another debate. I'm merely trying to point out that TC39 definitely took a conceptual left turn somewhere and ended up with a conceptually conflicted, trade-off ridden proposal with gotchas that cannot be conclusively described as worth while.
In fact, that was part of the motivation for using [[Define]] - so you didn’t need to know about superclasses to statically know the entire (modulo arbitrary modifications you make in the constructor, which always is both a possibility and something engines know how to handle) shape of instances (which engines know now, because fields appear statically and lexically in the class body).
In most other languages with classes, the entire structure of the class instance is known before even the first ancestor's constructor is run. This allows for situations like subclasses overriding functions that get called by ancestors during the constructor.
That is rather famously not true.
Much like different browsers implement JS in slightly different ways, C++ has also been implemented in slightly different ways over the decades. This is something that used to work with Borland C++ and Microsoft C++ back in the 90's. While it's still true that the derived class would not yet be initialized, the v-table would be. So it's not that the call would go nowhere (segmentation fault), but rather that the call would be sent to a function that may be expecting an already initialized set of instance properties. That means that making such calls is generally bad practice, not that making such calls couldn't be done. It may be hair splitting, but it's an important distinction.
BTW, pointing out 1 language where that seems to not be true doesn't invalidate my statement. I didn't say "every language with classes". Further, even if you had successfully invalidated this one point, that doesn't lend any more credibility to the contradictions and conflicts embedded in class-fields, or in any way shake the arguments I've made regarding the nature of ES. In fact, the simple fact that you chose to attack a side example instead of the main point may appear to add more credibility to the point I made.
If you wish to discredit my argument regarding the difference between classes and object factories, and how fields blurs the distinction, thus causing conceptual conflicts, please attack that directly. Unless your argument is logically flawed, I won't counter.
In fact, the simple fact that you chose to attack a side example instead of the main point may appear to add more credibility to the point I made.
Lord. No. I just didn't want readers to be come away with a mistaken belief about a question of fact.
Just as a note: The main reason for allowing such a thing is to handle cases where the base class knows how to contain and move information, but doesn't know the precise shape of it, while the derived classes know and need to be able to initialize that precise shape before the completion of the base class constructor. Admittedly, this is usually a sign that composition might be a better choice than inheritance. However, I'm of the mindset that a language developer shouldn't be in the habit of dictating what a language user should and shouldn't do (unless the particular practice is always catastrophic).