Open redradist opened 4 years ago
@bakkot I know it's been discussed to death, that's why I'm not going to argue with you. However, if at all possible, I would like an explanation. From the FAQ:
private x
?This sort of declaration is what other languages use (notably Java), and implies that access would be done with this.x
.
The part in bold is where I've always had issue. ES is a language where both a.b
and a["b"]
have always meant "public access". Why would the introduction of private x
in such a language suddenly cause a.x
to refer to private access? To my understanding, such an implication would only be possible if the presence of private x
also restricted the possibility of a "public" x
declaration. Since that is not the case (due to the desire for full encapsulation), on what grounds is it reasonable to claim the implication in the FAQ? I already understand the argument that this is how it is in other languages supporting the use of private x
, however I believe that argument invalid as in those languages, the aforementioned restriction exists as well.
As I stated before, I will not argue. I'm only looking for the justification for this supposed implication.
I think your questions are about “why would seeing this.x make someone think it’s private” - which i agree, It wouldn’t! To me, it’s that seeing “private x” would suggest to someone that they could access the private data with “this.x”, which of course they couldn’t, which would be very confusing.
@ljharb
think your questions are about “why would seeing this.x make someone think it’s private”
Um, no. My question is exactly as I stated it. It's about why "private x
implies this.x
", especially given that both a private member and a public member can be defined for the same instance object. Since it's already set in stone that this.x
is public, and that will never change without breaking the language, why would any new notation suddenly suggest that this.x
can be non-public?
@rdking because i don't think people think about public/private in that way. I think if they see private x
inside a class, they'll assume that this.x = privateData
is setting the data privately. The risk of doing this is that private information could be easily made public, which can have disastrous encapsulation consequences.
@bakkot I know it's been discussed to death, that's why I'm not going to argue with you. However, if at all possible, I would like an explanation. From the FAQ:
Why aren't declarations
private x
?This sort of declaration is what other languages use (notably Java), and implies that access would be done with
this.x
.The part in bold is where I've always had issue. ES is a language where both
a.b
anda["b"]
have always meant "public access". Why would the introduction ofprivate x
in such a language suddenly causea.x
to refer to private access? To my understanding, such an implication would only be possible if the presence ofprivate x
also restricted the possibility of a "public"x
declaration. Since that is not the case (due to the desire for full encapsulation), on what grounds is it reasonable to claim the implication in the FAQ? I already understand the argument that this is how it is in other languages supporting the use ofprivate x
, however I believe that argument invalid as in those languages, the aforementioned restriction exists as well.As I stated before, I will not argue. I'm only looking for the justification for this supposed implication.
@rdking Disagree with you, because if you say why private x
should cause a.x
to refer to private access, then why a.x
can always cause to return undefined, but setter still will work:
let obj = {
get x() {
return undefined;
},
set x(value) {
...
}
};
And from user point of view it can seem like x
has special behavior and it is !!
Also adding to name # break SOLID, Single responsibility
Name in such case serve two separate goal - access modifier and name of property ...
It is not extensible enough, because what if in future you will add another modifier, how it will look ... ???
Like this -#x
or like this %#x
...
There is reason why other languages use private, protected ... because it shows intent directly that this field is private ...
Anyway we can argue about it, but actually I use such convention in Python like _
and __
, but it has drawbacks that it is not obvious that this name has special meaning ...
@ljharb
think your questions are about “why would seeing this.x make someone think it’s private”
Um, no. My question is exactly as I stated it. It's about why "
private x
impliesthis.x
", especially given that both a private member and a public member can be defined for the same instance object. Since it's already set in stone thatthis.x
is public, and that will never change without breaking the language, why would any new notation suddenly suggest thatthis.x
can be non-public?
@rdking Acctually I do not understand why a.x
access is backed into language that it always should be accessed ?
@ljharb
i don't think people think about public/private in that way.
So to be clear, it's just an assumption?
@redradist
Disagree with you, because if you say why private x should cause a.x to refer to private access, then why a.x can always cause to return undefined, but setter still will work:
2 things:
private x
should cause a.x
to refer to private access. I did, however, state that this is an implicit assumption in the presumed implication. I think it's rather off-putting of TC39 to have such an assumption buried in their justifications.a.x
is private.Acctually I do not understand why a.x access is backed into language that it always should be accessed ?
If you're asking why a.x
must always be public, the short answer is that it doesn't have to be. If TC39 had not adopted the stance of wanting full encapsulation for private fields, then by simply disallowing the existence of a public field with the same name as a declared private field for any given object (as is the case in most languages supporting class
), it would be possible to allow a.x
to access the only x
available on a
.
That's not what they chose, and their reasons are sound enough. I don't necessarily agree with the notion of weakening a language to protect against sloppy or bad programmers, but I also cannot find fault in their reasoning. Since TC39 chose full encapsulation, regardless of what the private field definition token looks like, there's no way that the access expression can ever be a.x
for a private field.
@rdking anything that's based on people's possible mental models is an assumption. Assumptions are fine, anything that involves human thought can't truly be objective.
@ljharb
IMO, assumptions have their use. Justification is not among them. This is even more true when designing a very popular, widely used language. But maybe that's just me.
What if #foo
was kept as "hard private" like it already is, and then a new private foo
was a softer alternative introduced later (where this.foo
can only access one particular instance variable with a certain access level)? Would that be worth having in addition?
Why? The word "private" is wildly inaccurate if it's not "hard private".
@trusktr Unfortunately, that would introduce confusion in the extreme. To make something like that work, a class
wouldn't be allowed to have both private x
and #y
. You'd be forced to use either one or the other. It's not that it's not possible to do both in the same class, but that invites confusion. There's also the problem that if private
is used, it would need to be impossible to add new members to an instance that have the same name as a private
member. In short, while it could be done, it just doesn't seem like a good idea to have both notations.
@ljharb
Why? The word "private" is wildly inaccurate if it's not "hard private".
Um... your absolutism is showing. Think about it like this. If I have a letter in my hand, and I successfully keep you from snatching it away, the contents of that letter are private
to me. Who cares if you can see it in my hand. This is what private
means to most developers. The only ones doing something screwy that violates your idea of private
are those who are doing non-language related things like trying to get around sloppy developers who don't maintain proper versions on their shared code. That's not an issue for the language, but for the shared code developers. While I understand that it's still a pretty big issue, I still can't say that it's within the domain of the language itself to fix (unless the language dictates a library format).
Why? The word "private" is wildly inaccurate if it's not "hard private".
@ljharb It does not matter that you do not like "private" keyword or it has inaccurate meaning ... TypeScript already use it, this keyword has been widely used in ecosystem and adopted by other languages ... For me those "+"-es more reasonable than it may have inaccurate meaning, because someone think such ...
If you will introduce new private field variable it will split ecosystem again ... You are trying to design language but it is already designed by TypeScript team mostly in good direction ... Just standardize TypeScript (without "namespaces") ;)
Also even if you will implement private fields like in this proposal how to implement than protected modifier ?
Like this _#x
, -#x
... ?
Instead of thinking about some minor features like private fields, it would be better if TC39 improved current language by removing at least from language implicit type coercion ...
To make something like that work, a class wouldn't be allowed to have both private x and #y
@rdking Did you mean #x
there? I don't see why both aren't allowed (they are both allowed in TypeScript, and it would carry over well, for example). Here's a simple example:
class Foo {
#x = 123
private x = 456
// x = 789 // this would be a runtime error, private x already declared.
test() {
console.log(this.#x) // 123
console.log(this.x) // 456
}
}
const f = new Foo
// f.#x // this would be syntax error like it already is.
// f.x // would be a soft-privacy runtime error, "x is private in Foo"
// f.x = 987 // would be a soft-privacy runtime error, "x is private in Foo"
class Bar extends Foo {
// private x = 123 // would be a runtime error, private x is already defined in Foo
}
Other OO languages have soft privacy; there is precedent for it.
Why? The word "private" is wildly inaccurate if it's not "hard private".
@ljharb Saying that we must have only hard privacy is merely an opinion.
Why don't we have both options and let developers choose, and make their own opinions?
Or even better: why not take the opinions of non TC39 developers into higher consideration based on the high number of ignored votes they've placed?
In short, while it could be done, it just doesn't seem like a good idea to have both notations.
@rdking Yeah, ideally we would have only one thing and it would be better than the current, but I think it might be nice to have another option (TypeScript already has both) if we can't reverse the current state.
If the private x
version existed too (and even if it behaved like a normal prototype property except with restrictions on where it is accessed), I would find that to be very awesome and very usable and would prefer it over #private
fields any day.
Furthermore, if I really really wanted people to see a property's usage site and immediately know the property to be private, I could easily adopt a double-underscore-prefix convention for that. F.e.
class Foo {
private __foo = 123
method() { console.log(this.__foo) }
}
(I already use this convention in all of my code!)
@rdking something being "absolutism" doesn't invalidate it, unless you're absolutely against absolutism :-p "I successfully keep you from snatching it away" would require also denying me any means of reflection, which is indeed what "hard private" means.
@redradist please read through all the closed issues in this repo, in which "protected" is discussed in great detail.
@trusktr we don't operate on the basis of votes; every opinion voiced here has been considered, as far as I can determine.
Why don't we have both options and let developers choose, and make their own opinions?
Great question! That's already possible. Developers can, and already do, use a convention (leading underscore), or Symbols, or documentation, to mark things as "please don't touch". What's missing is the capability to have "hard private" for instances without jumping through WeakMap/WeakSet hoops, which is what this proposal provides. For "soft private", I'm not sure a compelling case can be made that special syntax (something that has a high cost to add) should be added that can do a better job, or be easier to write, than _foo() {}
or [Symbol.for('foo')]() {}
or similar. However, "hard private" is a new low-level capability, which is often sufficient to warrant adding new syntax.
@ljharb
"I successfully keep you from snatching it away" would require also denying me any means of reflection, which is indeed what "hard private" means.
On this, we agree, almost. I think it would be better to say that "I successfully keep you from snatching it away" only requires an impervious defense against any attempt to access. On this point we, need to be clear. Reflection is the technique by which the structure of a thing can be revealed. However, there's nothing specific to the concept of reflection that allows you to access what has been revealed. Conflating revelation and access is problematic to say the least.
What's missing is the capability to have "hard private" for instances without jumping through WeakMap/WeakSet hoops, which is what this proposal provides.
You missed a few things. Also missing are:
One of the many things that have bugged me throughout this process has been the mischaracterization of the concept of "soft private". If "hard private" means to be both hidden and encapsulated, then shouldn't "soft private" at minimum mean encapsulated"? If so, then Symbol and approaches like the _
convention are not "soft private" at all. These things are simply public as they always point to a publicly accessible property. So to say something like:
That's already possible. Developers can, and already do, use a convention (leading underscore), or Symbols, or documentation, to mark things as "please don't touch".
while trying to characterize "soft privacy" seems to miss the mark by quite some distance. If it's not encapsulated, then it's not "private" at all.
@trusktr
@rdking Did you mean #x there? I don't see why both aren't allowed (they are both allowed in TypeScript, and it would carry over well, for example). Here's a simple example:
It's just personal opinion, but for me, having them both accessible in the same class just invites potential confusion and errors. Having both "hard" and "soft" privacy in the same language would be good as it invites flexibility. Allowing both to be expressed in the same class
feels like a recipe for disaster. My thinking is that for the sake of sanity, usage of "hard" or "soft" privacy on a per-class
basis should be consistent. But that's just my 2 cents.
Given that, unlike many languages, in JS, a subclass can be created dynamically and at any time, anything "protected" is indistinguishable from "public".
Guys, why private
in JavaScript could not mean hard
private ?
I do not think it is needed to have "hard" and "soft" private ... there are lots of confusion ...
@ljharb
Given that, unlike many languages, in JS, a subclass can be created dynamically and at any time, anything "protected" is indistinguishable from "public".
You paint with such broad strokes! What you're failing to understand is that it is not an issue of security as is the case for "private". Sure, someone could dynamically create a subclass and gain access to the "protected" members. But so what? That doesn't make them public. It only makes them accessible from within a given scope that just happens to be broader than the scope for private, but more restricted than the scope for public... which is exactly what it's supposed to be. No surprise there.
When all is said and done, I don't care about "protected" that much. I've got bigger issues with how "private" is going to be implemented. I won't bother re-hashing them since that's just beating a dead horse. I'll just say this...
Where this proposal is concerned, all we really need are 2 things:
@redradist
Guys, why private in JavaScript could not mean hard private ?
There's no reason it can't. It all goes back to the FAQ and that poorly based assumption I addressed here.
“#” so bad
Having both "hard" and "soft" privacy in the same language would be good as it invites flexibility. Allowing both to be expressed in the same
class
feels like a recipe for disaster. My thinking is that for the sake of sanity, usage of "hard" or "soft" privacy on a per-class
basis should be consistent.
That would be totally fair allowing a class to have only one form; it would give users of the language a better set of options.
Dear Santa, please give me protected
and private
for one of these holidays.
@glen-84 Reading through those posts just reminded me of the likely reason we're in this debacle in the first place. Some library authors of popular packages want to be able to lock developers out of the internals so that there's less risk of breaking downstream packages when updating the internal logic of the libraries. It just hit me again how silly this argument is, and how simple the solution can be without getting us into the mess private fields is bringing.
It's a shame that these 3 points do not seem to coincide with the understanding being applied to this proposal.
@ljharb Knowing that you disagree with my post, I'd like to know why, either in a different thread or on discourse. Understanding why anyone in TC39 can't agree with this kind of understanding is critical to understanding the mindset of those who would champion or even advocate new proposals.
Why not give most of us the less-hard privacy with the private
keyword (still inaccessible at runtime even if the existence is clearly known, as described above), then let those who really wish to have the harder privacy to simply go out of their way to use WeakMap
?
Supporters of this proposal (a minority) tell the majority of people to go out of their way to use features like Symbol
(which is inconvenient and more verbose) if they want soft privacy. But that's not what the majority wants.
A flip of priorities seems like the logical solution: opposite of the current proposal, and based on community feedback and not a result dictated by a mere handful of people.
Or better yet, give us both, as described above, so that the majority of people can have a decent option, while those few can still use the current proposal's features if they really need to.
I've been thinking about the whole "protected" thing and realized that actually there is kind've a way for protected to work that isn't accidentally public. I realized this because this pattern is somewhat already present with the Promise
constructor:
class SubPromise extends Promise {
#resolve;
#reject;
constructor() {
// Resolve and reject are essentially protected state, instances are given
// copies and can do what they want with them (including making them public)
// however even with this I can't capture resolve/reject from an arbitrary instance
super((resolve, reject) => {
this.#resolve = resolve;
this.#reject = reject;
});
}
}
The main problem with this style of initializer pattern is it can be hard to add to existing APIs after the fact. Something like protected would help alleviate these cases where you want to expose a bunch of stuff to the subclass, but don't have anywhere to put it (e.g. due to using variadic args to constructor).
For example:
class SomeClass {
protected #newHook;
#data;
constructor(...data) {
this.#data = data;
}
}
class SomeSubClass {
constructor() {
super();
// Because it's on super not this, there is no way inside SomeSubClass we can access
// protected properties of an instance of another subclass of SomeClass
super.#newHook();
}
}
@Jamesernator the pattern of passing hooks up through super is viable, but we don’t need any new features for that - just a convention. Your second example doesn’t really work because a subclass can be created at any time, both of SomeClass and also of SomeSubClass.
Your second example doesn’t really work because a subclass can be created at any time, both of SomeClass and also of SomeSubClass.
Other subclasses of SomeClass
can't access protected properties given to SomeSubClass
, however you're right that subclasses of subclasses would need to be considered. Explicit forwarding would be the only safe way.
Also I actually realized passing hooks like in my Promise
example doesn't work, it throws a type error because the private fields haven't been initialized when the initializer is called.
class SubPromise {
#resolve;
#reject;
constructor() {
super((resolve ,reject) => {
// Oops #resolve isn't created yet as super() isn't finished, so this throws a TypeError
this.#resolve = resolve;
});
}
}
The real pattern would have to be:
class SubPromise {
#resolve;
#reject;
constructor() {
let resolve_;
let reject_;
super((resolve, reject) => {
resolve_ = resolve;
reject_ = reject;
});
this.#resolve = resolve_;
this.#reject = reject_;
}
}
This is a lot more awkward and error prone to deal with than the strawman protected #state
I proposed above.
@Jamesernator If you're looking for a way to do protected state that isn't accidentally public, not only is it possible, I showed and early prototype of it to @ljharb, and through his feedback actually worked out how to solve the direct issues he pointed out. I've been using the results of that in my own projects since then. It's a small solution but not exactly simple. A full example of the logic for this is here. This approach doesn't involve passing anything through the constructor. Further, if it were implemented in-engine some of the complexity could be reduced due to having access to internals.
Note: At the time of that discussion, despite the functional logic, @ljharb still maintained a strong stance again inclusion of protected
.
it looks # is used for this record and tuple
https://github.com/tc39/proposal-record-tuple
class Something {
#test1 = #[1, 2, 3]
#test2 = #{ hello: 'kitty' }
}
so this look not so good
That’s an argument against Records and Tuples, not against already-shipping stage 3 class fields syntax.
Hi,
From my point of view it seems like mistake to use # for private fields, because it is not extensible ... If in future we would want to use protected field, it will make harder to implement it ... -# ??!
I think we should not reinvent the wheel and just to use time proof solution with
private
andprotected
keywords ;)