Closed rdking closed 5 years ago
For now in the documentation, the keyword will be priv
. It's close enough to private
without carrying all the legacy implications and Typescript stumbling blocks.
What about this?
class A {
private [somePrivate] = 2;
someMethod() {
assert(this[somePrivate] === 2);
assert(typeof somePrivate === 'symbol');
}
}
Like I said before, I'd love to, but I don't want to deal with the push-back over Typescript's use of private
and the pointless expectation that private x
implies this.x
.
This means let and const feel out of place since these terms define variables instead of properties.
I don't think this is a very important issue to most programmers. If field
is an accept term, then variable
will be very ok even it's technically a property
. Some other programmers just use var
for both local vars and class properties.
So I think let/const
still a valid choices (without the private x
implies this.x
issue even I also agree it's weak).
I think let [someSymbol] = 2
syntax (or priv [someSymbol] = 2
) has a big issue (just like current field proposal), syntax ambiguity with destructuring. This is much worse than "private x
implies this.x
". We'd better still use let x = 2
and desugar it to auto generated symbol for ergonomic.
You already know I have some other keyword candidate to map private/protected/public
, but I think priv x = 2
is also ok, if you use both priv/pub
. If you only want keyword for private
I think internal
is also a good candidate (though 8 letters seems too long for ergonomic).
I also vote for keeping let/const
regardless of implementation details.
imo let/const can’t possibly make sense if you’re accessing them on an object - ie, if you have per-instance data, and you need to access them on multiple objects at the same time.
@ljharb Dart, Scala, Swift, Groovy, Kotlin... All new languages use same keyword for both local vars and instance vars. I don't understand why JS can't.
I should qualify my comment: it only applies in the context of this proposal. More generally, this is one of the reasons I prefer the class fields proposal: field/property declarations look like field/property declarations. But without a prefix like #
, I think let/const declarations would be the next best thing.
@hax
I don't understand why JS can't.
The problem is that ES already has a history of not doing things that way. There is absolutely no way to declare a property using var/let/const
. On top of that, you don't want to conflate 2 mental models that have a long history of being kept separated. Variables and properties are such things.
As for priv [x] = 'foo';
. I dropped that syntax. I couldn't rationalize the reason why I wanted it against the costs of having it. So it's gone. The README has more updates.
IMO using priv
instead of let/const
significantly worsens this proposal. Although you seem to insist on dismissing this point as invalid, the fact is that some developers (the majority, I suspect) will expect private x
to be accompanied by this.x
for access. And I don't think the expectation would be different for an abbreviation like priv
. One of the reasons I have considered this proposal a potentially viable alternative to class fields is the let/const
syntax, which doesn't come with that baggage (although of course it comes with a lot of other baggage). (Same with classes 1.1 and the var
syntax, but I prefer let/const
.)
Although I don't like this idea either, just to throw it out there, I think local
would still be better than priv
, if you insist on getting rid of let/const
.
@mbrowne I'm not insisting on dismissing your point. I just need a stronger argument to get me past the conflation of variables and properties. Your argument about other languages doesn't work for me because those other languages have had that conflation in them from the beginning, whereas ES has kept them separate since the beginning. I need an argument that says they can be conflated without breaking the mental model differences between them in ES. Otherwise I just can't do let/const
.
If you can give me a strong-enough argument, I'll be happy to reconsider.
@rdking I understand that why you think let/const
should not be used for property. I can't give you a "strong-enough" argument, I only can give you a relative strong argument..
I think long history make nothing more valid, especially every one to two years the js community are doubled, so it's not a really "old" language if you consider the community. There are also many (don't know the accurate number) js programmers also use other languages, especially Dart, Swift, Kotlin which are popular languages for client-side developing. So I think at least fullstacks engineers and those who do both web developing and client developing, have no issue about the conflation.
On the other side, if you consider current field proposal ,which break too many current js syntax/semantic, you can believe the conflation cost is ok for get rid of other much huge cost! 😂
We’re designing for all future JS devs, the majority of whom will likely never have used another language. It’s helpful to take lessons from other languages, but JS is its own thing, and it’s ok if we deviate from other languages in order to achieve a more consistent mental model.
it’s ok if we deviate from other languages in order to achieve a more consistent mental model.
Agree, but before that you should not break current mental model of JS. I will say most programmers will agree current proposal fail on that as the feedbacks we got.
I think I just came up with a way to justify continuing to use let/const
.
@ljharb I need you to give what I'm about to suggest some real thought. I know you mentioned that there's a seeming embargo on shorthand notations, but in this case, it may turn out to be a saving grace. With this idea, I even fixed the imbalance problem that led me to give up on shorthand notation in the first place.
::
to ensure the separation of private and public scopes.let/const
Require all members to use the same namespace, but only within the class
definition.
Basically, this would mean it would be impossible to declare a public and private member with the same name. However, it would be perfectly fine to later attach a new public member that happens to have the same name as an existing private member.
class
definition.let/const
class
a more "traditional" feel when coding member functions.//Throws due to duplicate name in class definition
class Bad {
let x = 0;
get x() { return x; } //Syntactically ok, but weird
}
class Good {
let _x = 0;
let y = 1;
get x() { return _x; } //Because we can't duplicate names
print() { console.log(`y = ${y}, x = ${x}`);
}
var g = new Good();
g.print(); //y = 1, x = 0
g.y; //undefined
g.y = 3; //This is ok because private members are still in their own namespace
g.print(); //y = 1, x = 0
g.y; //3
What about let ['x'] = 0;
or let [var] = 0
in the first example? Would it throw at construction time, or at class evaluation time? (or would that syntax not be allowed, to keep it static?)
What happens if the class does Object.keys(this).map(k => this[k])
after g.y = 3
. Would it include the y
? What if it did return this.y
despite not having any y
declared anywhere? Would it return undefined
and then later 3
, or would it throw because there's a let y
?
@ljharb That's a no-go. I removed that possibility earlier. It would only be good for creating Symbol-keyed private properties, but since private property names are already non-enumerable, I couldn't conceive of a reason to need Symbol names for private members. I'm still open to arguments on that.
What happens if the class does Object.keys(this).map(k => this[k]) after g.y = 3. Would it include the y?
y
is private, and therefore not enumerable.
What if it did return this.y despite not having any y declared anywhere? Would it return undefined and then later 3, or would it throw because there's a let y?
I'm not sure I understand this question. If you're asking what would happen if there was a function in the class that ran return this.y
, then yes, the first g.y
would return undefined
and the second would return 3
. When you don't use shorthand notation, you get exactly what you asked for.
i'm talking about the public y
property - it'd have to show up in Object.keys
, so what happens when they try to access it? If you're saying that this.y
always is the public property, then great, that all makes sense!
Given that, what's the benefit to denying class authohrs the ability to do y() {}
or get y() {}
when they have a let y
?
Oh. OK. this.y
is enumerable, so before being set, it wouldn't exist to be enumerated. After being set it would exist as enumerable. So after g.y = 3
, the public y
would be included in the map.
The thing to remember is that the rule about keeping public and private members within the same namespace only applies within the lexical scope of the definition during the evaluation of that definition. Even the constructor of the class is free to add new public properties that share the name of private ones.
@ljharb
Consider current js allow class X { x() {}; get x() {} }
, it seems we could also allow class X { let x; get x() {} }
and leave the duplicate naming issue to linters. Anyway, I think it's a relative small bikeshed issue.
@hax Nope. Can't do that. If it's possible to declare any 2 properties with the same name inside the lexical scope of the class, the whole house of cards falls.
@rdking Obviously you can't allow class X { let x; const x }
, but I think class X { let x; get x() {} }
may be possible, because you use x
in class to access let x
, and use this.x
in class to call get x() {}
. Though we'd better use linter to avoid that.
The problem with having a public and private member with the same name is that it completely imbalances any attempt at shorthand notation. Since there can be duplicate names, which one is going to be the one that gets accessed when you use the shorthand? This means you'd only be able to shorthand either the public or the private, but not both. But if you can't do both, it's not worth doing either.
not sure, temporarily deleted.
Example of some pointless but perfectly valid code.
class X {
let x = 1;
let y = 2;
constructor() {
Object.defineProperty(this, "x", {
configurable: true,
get() { return x * 4; }
}
this.y = 7;
y++;
}
print() {
console.log(`(private) x = ${x}`);
console.log(`(private) y = ${y}`);
console.log(`(public) x = ${this.x}`);
console.log(`(public) y = ${this.y}`);
}
}
/* Prints
* (private) x = 1
* (private) y = 3
* (public) x = 4
* (public) y = 7
*/
(new X).print();
In general, any property declared within the class must have a unique name. All static
members constitute a separate namespace from the non-static
members. So it's possible to duplicate names that way. But within each of these namespaces, duplication is not allowed during class
evaluation.
Static
members are properties of the constructor, so they are shorthand eligible only for static
member functions.
Since all non-static
members have unique names, they are shorthand eligible for non-static
member functions and are added to the execution context of member functions as if the "with" keyword had been used. Properties added later (like in the constructor or outside the class) are not eligible for shorthand and require the full notation to be accessed.
@rdking You're making a big deal about the upcoming implementation change and saying it completely invalidates the closure concept, but I don't understand why. Aside from proxies forwarding to private members, it doesn't seem like much would change for developers in terms of the observable behavior of private members (you mentioned protected and friend earlier but I think that's a separate discussion). And since there will be an intermediate container object for private members, the notion of separate "private properties" doesn't precisely reflect what's going on under the hood anyhow.
Also, the use of shorthand syntax obviously makes it seems more like a closure, not less, so I'm not following your argument there. But I think the intuitiveness of let/const and shorthand syntax for access might be a good thing for this proposal anyway (although of course there's still the learning curve of understanding that these members are instance-level and not static).
of course there's still the learning curve of understanding that these members are instance-level and not static
See https://github.com/hax/hax.github.com/issues/44 (note it use var
in old classes 1.1, but we can agree let/const
should have the similar result). So I don't think there's a high learning curve.
@mbrowne The original update did invalidate the closure concept. It just wouldn't work as long as it was possible to declare 2 different properties with the same name. However, as of the latest update, that's not a problem anymore. The mental model can go back to the closure concept. Like you said, the solution for the shorthand problem does indeed drive the model home.
The README for this proposal has been updated. As per @mbrowne's suggestion, information regarding the implementation details has been moved to "/docs/implementation-overview.md". There is a link to this at the bottom of the README.
Here's the problem. I'm re-writing the README in terms of the new suggestion in #10. This means that instead of having "instance-closure variables", I now have "instance-private properties". This means
let
andconst
feel out of place since these terms define variables instead of properties. I already know that, even though I desperately want to, I can't useprivate
since it will get shot down over the insanely weak "but I expectprivate x
to be accessed withthis.x
" argument. Even PHP didn't uphold that expectation...Anyway, I'm open to suggestions. If it weren't for the fact that Symbols can be property names, I would have kept
let
andconst
. However, who wants to deal with developers thinking that because they can writelet [someSymbol] = 2
in aclass
definition, that it's ok to do that in a function? I know I don't.