Closed lifaon74 closed 6 years ago
@claytongulick, for context, we've been thinking about how to go about adding private fields to the language for well over a decade (I have a hard copy of what I believe to be the very first proposal for adding classes to the language, from around 1999, which mentions private fields as something to think about adding once we figure out a good way to go about it), with the sigil-based solution going back... I don't know how long, but at least several years.
As such, I don't think "aggressive" is really an accurate characterization: this is a core feature, difficult to achieve in userland without language support, and discussed for years. I don't see how we could be much less aggressive, and I don't think we have much to gain from holding off any longer.
I can see us (as a community) potentially kicking ourselves for burning this character to solve a problem for which there are already alternate solutions.
I share this concern.
I just remember some of the cheats that PHP put into place to solve technical problems. They later became hated features of the language that have either taken years to remove or developers are just stuck with. While the technical problems may be better considered here, I still think it a cautionary tale for this current proposal.
That said, I really appreciate all the thought put into #
. It is obvious the technical concerns were very carefully considered. I hope that same attention detail and careful thought can be applied to a solution that is more extensible and palatable to the community.
@blargism
I hope that same attention detail and careful thought can be applied to a solution that is more extensible and palatable to the community.
Is there any reason to believe such a solution with technically acceptable tradeoffs is out there waiting to be discovered?
To the contrary, I'm reasonably confident it is not, for the reasons given in the FAQ (and, as I say above, because we've been thinking about this for decades).
@bakkot I don't think anyone doubts the level of effort you've clearly put into this - we in userland have been tackling this problem for quite a while too, and have come up with several different effective solutions.
I see that proposal commit you linked from two years ago in 2015, and I understand why @
was abandoned in favor of using it for decorators. Of course we're all familiar with some of the history of the debates that drove the current form of classes in js and a lot of the discussions about the form they should take. Indeed, if we're talking about decades I have no doubt that Brendan Eich was thinking about it in 1995 when he invented Javascript, two decades ago.
Guys like me that cut their teeth coding on TRS-80's and asm in DOS (because C was too slow) certainly appreciate the evolution of languages and the careful consideration that goes into them. I think you're one of these as well. We also have seen mistakes made in languages that have far and long reaching consequences, not that this is necessarily one of them. I'm sure you can appreciate, however, that given the history of js, the last four or five years have seen a pretty massive acceleration in the rate of change. This is a good thing. That doesn't mean that every feature needs to go through on such an accelerated schedule.
You've received a ton of feedback from the community on this particular one. We have lots of current options for encapsulation of privates that we in userland have been using for many years, and they work fine.
I don't think we have much to gain from holding off any longer.
Is there any reason to believe such a solution with technically acceptable tradeoffs is out there waiting to be discovered?
To the contrary, I'm reasonably confident it is not, for the reasons given in the FAQ (and, as I say above, because we've been thinking about this for decades).
I'm curious - the whole point of having the proposal stages with TC39 was to have public review and gather feedback. I see this happening, which is a great thing. It's curious and strange to me that your response is to point to the FAQ, and to operate under the assumption that this is the only correct way to move forward? I thought that this exact type of situation is the reason why we had the progressive stages of approval, to make sure we're getting it right, and to collect feedback, and to slow down.
I've been developing in JavaScript since 2004. Not your auspicious "decades", so I'm pretty new at this.
Also @bakkot, if this the best you can come up with in two decades, maybe someone else should take over.
The current state of encapsulation in JS codebases that use classes or traditional constructor functions is abysmal. This proposal obviously recognizes that the lack of a simple way to create private properties using conventional class syntax is a problem, and offers a solution. IMO a concise syntax that makes it easier for developers to achieve proper encapsulation is a very good thing. I realize this wasn't the primary reason (or maybe not one of the reasons at all) that the #
syntax was proposed. But encapsulation is a basic OO principle, and I think it's totally reasonable to dedicate a character to improve a core feature like classes.
In many languages, the default access level is private or internal. Given that the default access level for JS will alway needs to be public (for backward compatibility), using a single character to indicate private
is a great idea to encourage good practice. We've gotten along this far without needing #
for other features, so it's hard to imagine that whatever future features deserve special syntax couldn't be achieved with some alternative to #
.
Are there any other reasons for objecting to the #
character besides wanting to possibly use it for other things in the future? If someone is programming in a non-OO paradigm where encapsulation doesn't matter so much (perhaps because they only use immutable objects), they shouldn't feel constrained by the new syntax; they can simply not use it. Or is it a matter of valuing encapsulation but still disliking the syntax?
P.S. Just to avoid any misunderstanding of my above comment, I think it's certainly possible to do object-oriented programming without mutable properties. It just wouldn't be the traditional way of doing it, of course...and for anyone doing traditional OO, encapsulation is very important.
@claytongulick,
I'm sure you can appreciate, however, that given the history of js, the last four or five years have seen a pretty massive acceleration in the rate of change. This is a good thing. That doesn't mean that every feature needs to go through on such an accelerated schedule.
Sure, I agree. But, well - my point was, this isn't an accelerated schedule. Private state for classes was originally slated for ES2015 (and indeed ES4 before it), and was deferred to a future edition because the committee could not at the time agree on a single solution. This proposal is very much an outgrowth of that work, and comes after exploring quite a number of alternatives in the design space and ultimately rejecting each.
You've received a ton of feedback from the community on this particular one. We have lots of current options for encapsulation of privates that we in userland have been using for many years, and they work fine.
If the userland solutions worked fine, we wouldn't need this proposal. (Though I guess you could quibble about what "worked fine" means.) This is something a lot of people, particularly library authors, have been asking for for a long time.
It's curious and strange to me that your response is to point to the FAQ, and to operate under the assumption that this is the only correct way to move forward?
I would be delighted to discover a better way to move forward.
But: I wrote the FAQ precisely to explain the constraints we consider important, which explain why we didn't think any other way thus far proposed would work - after, indeed, a great deal of feedback. At this point I don't think it's fair to call it an assumption. I think there are fairly solid justifications for my belief. I would not be helping to advance this proposal otherwise.
"We think this is the best way to accomplish this" is not incompatible with "we are taking feedback".
I thought that this exact type of situation is the reason why we had the progressive stages of approval, to make sure we're getting it right, and to collect feedback, and to slow down.
Stage 3, the stage this proposal has just reached, is the stage designated for collecting feedback. We've already been doing so for years, and I expect we'll continue to do for some time.
To be clear, then, short of killing it, what would you like to see the committee do differently with regards to this proposal?
(That said, we've already collected a lot of feedback. A lot of it has been very valuable. The remaining feedback tends to be either "I don't like the syntax" - which is a very real cost of this proposal, but also very much one we've already weighed - or "I prefer reflection to encapsulation" or similar design tradeoffs which again we have already weighed, or an alternative we have already considered and rejected for reasons outlined in the FAQ. There isn't all that much value in getting the same feedback repeatedly.)
As an aside, I think "slowing down" per se was not a goal of the stage process. Actually, checking the notes, it is an explicit non-goal.
@blargism, to be clear, I'm referring to the committee and community as a whole, not myself personally.
@bakkot
to be clear, I'm referring to the committee and community as a whole, not myself personally.
Thanks for the clarification. To be fair, even the collective experience isn't objective proof of value. Just look at how much the experienced folks in the U.S. congress get done 🤣. I sit next to a guy who's two years into his development career an I am working towards finishing my second decade. He has still managed to teach me some things. Let's not rest on our laurels.
@mbrowne
Or is it a matter of valuing encapsulation but still disliking the syntax?
I do value encapsulation, I do dislike the syntax. There are some alternatives that can work almost the same and be much less irritating.
class SomethingPalatable {
private.a = 0;
private.b = 0;
protected.c = "A potential for the future";
}
class NotMyPreference {
#a = 0;
#b = 0;
// no clear path to protected
}
In this case #
and private
do almost exactly the same thing, and doesn't seem to have the same problems as the other alternate solutions addressed by the FAQ. In this case anything assigned to private
isn't available anywhere except in the base class, just like the proposed #
. The difference being it is more descriptive, clear, and provides a simpler path to more nuanced forms of access.
But encapsulation is a basic OO principle, and I think it's totally reasonable to dedicate a character to improve a core feature like classes.
@mbrowne I also forgot to mention that while encapsulation is a worth burning a special character, we can't do so without considering the future. Using #
provides no clear path forward towards protected
or a more innovative idea yet to be conceived. I agree we can't make a choice based on maybes, but we also can't ignore the fact that nearly every other language that embraces OO principles has the concept of protected
in some shape or form. Picking a solution for private
with no idea how to deal with protected
seems pretty close to an oversight.
@bakkot I think this is my favorite comment from the issue you linked, and I think it really sums up the issue well. Hard private v/s soft private, and the reasons for each.
I'm going to take a bit of a detour here to explain where I'm coming from and why.
I'm a javascript evangelist.
I don't mean in a public give talks at SXSW way, I mean in a day to day, developer by developer hard grind kind of way. For more years than I'd like to consider, I've been slowly educating Java and .Net developers that have irrational and almost rabid hatred towards js. Explaining about the benefits of dynamic typing for some classes of problems. Explaining how prototypal inheritance is different, but just as effective (if not more so) than traditional OOP inheritance. Describing the fundamentals of functional programming and when it's a superior methodology to traditional OOP. Etc... etc... etc...
See, guys like me, we think js's openness and functional approach are strengths, not something to apologize for. We agree with Python's "we're all adults here" approach. We desperately don't want to see the OOP mess that Java and .Net devs have to deal with every day infect the language we love. This is the "second camp" from @glen-84 's remarkable comment.
There's been so much push back on this feature, I feel like it would be a signal to the js community at large and js advocates like me that the standards process isn't really interested in what they/we want.
We're at a really interesting time right now, where Javascript the language is one of the most popular in the world, due mostly to the fact that there's a captive audience. But that's changing. With web asm, eventually we're going to get to the place where js is competing on its merits. We're going to need to be able to point to our language and be proud of every part of it to evangelize, and to convince the world that they should use it rather than just do a Java -> webasm or C# -> webasm, etc... and in this, syntax really matters.
The points from the 'second camp' are what are going to win and keep mindshare, if we abandon or lose sight of that, we risk becoming a footnote.
Ok, overly dramatic soliloquy complete - what do you think about @blargism 's proposed syntax? That's really intriguing - I'm not sure I've seen it in any of the discussions so far - private.foo = 'bar';
seems (on the surface) to be a neat compromise, and feels very javascripty to me - i.e., I know that private is some sort of magic object that has props with restricted access just by glancing at the syntax. Also would solve the dynamic lookup issue? With something like private["foo"]
?
@blargism
To be fair, even the collective experience isn't objective proof of value.
Of course! But at some point, when no one has been able to come up with a better alternative which achieves the feature's goals and is feasible given the constraints of the language, you start to suspect that there might not be one. That's all I meant to say.
class SomethingPalatable { private.a = 0; }
I intended that proposal (which has indeed come up several times before in some form: x, x, x, at the least, as well as in committee) to be covered by this question in the FAQ, though I guess that's not very clear. (To save a click: it would work for accessing fields of this
but also needs to explain how to access private fields of other instances, and there does not seem to be a clear workable way to do that.)
It also runs into the further, unrelated problem (mentioned those threads but not the FAQ) that private
is a legal variable name in non-strict code, and always a legal property name. So the following is currently legal code we can't break:
let private = { prop: 0 };
let o = {
private: { prop: 1},
m() {
return private.prop;
}
};
o.m(); // 0
Of course, you can't write private
as an identifier in strict code, including in a class body, so it is not technically impossible for private.prop
to be a reference to a private property when written inside of a class. But, uh, it would be weird.
With the private.x
syntax, how would you access a private field on a different object of the same class?
@claytongulick
See, guys like me, we think js's openness and functional approach are strengths, not something to apologize for.
I absolutely agree with this. Though I don't buy that this is an argument for reflection over encapsulation. Closures, for example, are as core a part of the language as anything you could name, and have never offered any sort of introspection: indeed, the type of privacy offered by closures has been a guiding example in the design of this proposal, including the lexical scoping of private fields.
We desperately don't want to see the OOP mess that Java and .Net devs have to deal with every day infect the language we love. This is the "second camp" from @glen-84 's remarkable comment.
Unless we're looking at different comments, that's exactly backwards: the "second camp" is the one which wants multiple types of accessibility modifiers all of which are only suggestions, which accomplish nothing not already doable relatively easily and clearly in the language as it is today and which lead to the sort of mess Java is currently trying to repair with modules.
By contrast, this proposal has been pared down to the absolute minimum necessary for its goals, on top of which other features like friend
access can be built - though, again, we aren't ruling out adding language-level support for such features at some future point.
There's been so much push back on this feature, I feel like it would be a signal to the js community at large and js advocates like me that the standards process isn't really interested in what they/we want.
You're missing that there is a large if quieter camp of people who really, really want true private state in the language. I've talked to a number of library authors who are impatient to start using this - the Node foundation, for example, is on the committee and, if I recall correctly, would like to start reimplementing their internals in terms of this feature as soon as it's available.
We care a great deal about both sides of this debate. But there is a fundamental conflict here between reflection and encapsulation. They cannot both be accommodated. Our mandate is to balance the needs of all users of the language, including future users (who among other things are generally less put off by new syntax than people who have gotten used to the language without it), and do the best we can within the technical limits of the language. That has to mean that, sometimes, we advance features despite vocal objections.
@bakkot - if you, some library authors, and TC39 have already decided that this is going to happen, why are we bothering with a review process?
That has to mean that, sometimes, we advance features despite vocal objections.
If the feedback you're gathering doesn't have an impact, why are you gathering it? Why bother with Stage 3?
You're missing that there is a large if quieter camp of people who really, really want true private state in the language.
Our experiences have clearly been quite different, and the feedback I've read on these github issues paints a different picture to me as well.
Unless we're looking at different comments, that's exactly backwards: the "second camp" is the one which wants multiple types of accessibility modifiers all of which are only suggestions, which accomplish nothing not already doable relatively easily and clearly in the language as it is today and which lead to the sort of mess Java is currently trying to repair with modules.
I think we're interpreting that differently. The whole point of this proposal and its defense has been that even though it's ugly, even though no one likes it, the #
syntax is the only viable technical solution that meets the hard private goals. However, that is not everyone's goal. In fact, it seems like it is the goal of a few people. It is remarkable and strange to me that the needs of a few highly technical library authors would drive a language's design, rather than the needs of the average js developer. I think that's how languages lose mindshare. I think TypeScript and Flow are doing a great job of illustrating my point - and they're transpiling. What do you think is going to happen when they can compile directly to web asm? Do you think the syntax in this proposal is going to help attract developers back to js?
If you're signaling that this is going to move ahead regardless of any and all objections raised, I'm not going to spend any more time on it. Participating in this discussion has been pretty eye opening for me - historically I've prevented my teams from using transpiled solutions other than babel, since it's my job in technical leadership to try to pick a winner for long term investment. This is honestly the first time I've started to wonder if js is going to be a long term winner. Maybe I should be more flexible evaluating transpiled solutions.
Even though we obviously disagree, I still appreciate the time you've spent discussing this with me and others! Best of luck.
@ljharb @bakkot
With the private.x syntax, how would you access a private field on a different object of the same class?
Why is this seen as a requirement? IMO private should mean private to the instance, not the class. Classes in JS are just a blueprint for creating new objects of a given type; after that, the only link between them is the prototype. This is a good thing because it means JS is actually object-oriented rather than class-oriented (although the current class implementation, in the absence of some sort of traits feature or embedding feature, lessens that a bit...but decorators will be a step in the right direction).
IMO, if we're talking about instance properties, the primary encapsulation boundary should be around the instance, and a different instance of the same class should be just as much outside that boundary as a different object of a different class. Of course static private properties can be useful too, and I'm not arguing against those -- for static private properties, the encapsulation boundary is indeed the class and not the instance.
@blargism
Using # provides no clear path forward towards protected or a more innovative idea yet to be conceived.
While I don't agree with the need for protected specifically (see my above comments), I definitely agree that there should be something between private and public. I was assuming that syntax to indicate such an access level was still on the table for future proposals. But if it's not, then even though I like the #
syntax, I strongly agree that we should have a plan for how to add proper support for internal/protected/whatever before this proposal is finalized, even if the syntax for that doesn't make it into this particular proposal.
I agree on some points: the current # are driven by a few and doesn't strongly care of ALL developers expectations or feedback and no metrics are available. The proposal was in stage 0 not so long ago and only a few feedbacks has been expressed until the stage 3 has been reached. We all agree on the fact that we want private but we should think about all aspects and consequences instead of hurry up.
For me things are pretty clear :
(That said, we've already collected a lot of feedback. A lot of it has been very valuable. The remaining feedback tends to be either "I don't like the syntax" - which is a very real cost of this proposal, but also very much one we've already weighed - or "I prefer reflection to encapsulation" or similar design tradeoffs which again we have already weighed, or an alternative we have already considered and rejected for reasons outlined in the FAQ. There isn't all that much value in getting the same feedback repeatedly.)
Not so much feedback and a biased sample ! I didn't see any google form or whatever with stats gathering thousands of answers... Each proposal impact millions of developers, we should not hurry up too much... The "I don't like the syntax" should be taken in account. Personally the # is fine to me. The "I prefer reflection to encapsulation" is even more important because it's critical real problems It's like saying : I prefer donuts over cake, so everybody will eats donuts until the end of their life... inacceptable. Both should be available.
@mbrowne
With the private.x syntax, how would you access a private field on a different object of the same class?
Why is this seen as a requirement?
I spent a lot of time questioning this too, but the case that convinced me was that this enables things like binary methods:
class User {
#socialSecurityNum;
constructor(ssn) {
this.#socialSecurityNum = ssn;
}
isSamePerson(user) {
return user.#socialSecurityNum === this.#socialSecurityNum;
}
}
It also makes sense from a visibility perspective: If you have a private field, the only code that has visibility into it is the class's members. Thus, the logic that operates on the private field of one class instance is the same as the logic that operates on another class instance.
Moreover I couldn't find a compelling example of why this kind of visibility would be dangerous.
@claytongulick
If the feedback you're gathering doesn't have an impact, why are you gathering it? Why bother with Stage 3?
I definitely don't mean to say that the feedback doesn't have an impact. Among other things, it's always possible we've missed something. It's just that getting the same feedback we've already received - for example, that many JS developers encountering the syntax for the first time find it distasteful - is unlikely to change at least my own mind, because we already know that and have weighed its cost.
The whole point of this proposal and its defense has been that even though it's ugly, even though no one likes it, the
#
syntax is the only viable technical solution that meets the hard private goals.
For what it's worth, "no one likes it" isn't true. Few people like it when encountering it for the first time, but it tends to even out after a while.
And - the #
syntax is also the only technically viable solution that meets the soft private goals, except for the "no new syntax" goal. (Indeed, we were considering using it for soft private for a long while.)
Either way, having a subclass's public fields clash with a superclass's private fields is, I think, unacceptable for private state - otherwise you can get by just fine by prefixing things with underscores. Either way, a method expecting to operate on a private field x
of an instance of the class shouldn't be operate on the public field of the same name when given a non-instance with a public field x
. Either way, any solution must not significantly slow property access, because the speed of property access affects every user of the language including every visitor to every webapp, not just developers of classes. Either way, adding a new a private field x
shouldn't break all other property accesses of a property named x
anywhere in the class. Either way, the this
keyword should not get even more special behavior.
It is remarkable and strange to me that the needs of a few highly technical library authors would drive a language's design, rather than the needs of the average js developer.
To a large extent, serving the needs of library authors serves the needs of the average JS developer, because the average JS developer relies on libraries and so relies on library authors being able to do their thing.
That said, I dispute that the average JS developer would prefer to write classes which have private state as a suggestion rather than private state as a thing enforced by the language. If the average developer is using reflection to get at private state, something has gone very wrong, and whatever "private" state is present should probably never have been added.
I think that's how languages lose mindshare. I think TypeScript and Flow are doing a great job of illustrating my point - and they're transpiling. What do you think is going to happen when they can compile directly to web asm? Do you think the syntax in this proposal is going to help attract developers back to js?
I don't think "maximizing mindshare" is actually a goal of the committee, else we wouldn't've been enthusiastic about wasm. We are just trying to evolve the language to the best thing it can be. That said, I don't expect the addition of this feature to the language will significantly turn people off of it.
(As a total aside: it is highly unlikely that TypeScript and Flow will ever compile to wasm as anything more than a party trick. Wasm is more for languages like Rust, not languages which need the support of a complex optimizing runtime which is already built in to the browser.)
If you're signaling that this is going to move ahead regardless of any and all objections raised, I'm not going to spend any more time on it.
I'm sorry to have given this impression; it is not the case. I just meant to say that we've thought a lot about this topic, including all of the objections raised in this thread several times over. Which does not mean "please don't give feedback". It doesn't even mean "please read all of what we've written about this proposal before giving feedback", or else I would not be in these threads responding to the same objections.
It just means that our response to objections already considered is mostly going to be "here's why we're still behind this proposal despite those particular objections".
@jeffmo Your example could be accomplished by exposing a public getter, like many other languages do.
class User {
#socialSecurityNum;
constructor(ssn) {
this.#socialSecurityNum = ssn;
}
ssn() {
return this.#socialSecurityNum
}
isSamePerson(user) {
return user.ssn() === this.#socialSecurityNum;
}
}
@Akkuma Yes, but then it wouldn't be private.
I'm not aware of any language that has both class
and a notion of private fields in a class where the accessibility of those fields is per-instance rather than per-class. I listed a few examples here, though that list could be substantially longer.
Right, the constraint I probably should've called out is that you should be able to create an instance of User
with the ssn, then forget the SSN and pass those User
instances around without any concern that other code might be able to read the SSN.
Has there been any discussion about pulling private class fields into a separate proposal? I realize that it's asking quite a bit, but I do feel like it would allow for certain undisputed additions to the language to continue to move forward while discussion continues over private class fields.
It seems to be a widespread opinion that static
fields and instance fields are much more cut and dry than the current private fields proposal.
I definitely don't buy the argument that there aren't a lot of developers who want this feature. Some perspective from TypeScript - "Private fields aren't actually private" was one of the first 10 bug reports we got. People have been demanding "true" private fields in classes for 5 years now. There is a sizable contingent of people who want this feature:
http://typescript.codeplex.com/workitem/7 http://typescript.codeplex.com/workitem/281 http://typescript.codeplex.com/workitem/912 https://typescript.codeplex.com/discussions/397651 https://typescript.codeplex.com/discussions/398585 https://typescript.codeplex.com/discussions/448054 https://typescript.codeplex.com/discussions/451129 https://github.com/Microsoft/TypeScript/issues/564 https://github.com/Microsoft/TypeScript/issues/1537 https://github.com/Microsoft/TypeScript/issues/3151 https://github.com/Microsoft/TypeScript/issues/9733 https://github.com/Microsoft/TypeScript/issues/9950 https://github.com/Microsoft/TypeScript/issues/11033
This comes up so frequently we have a FAQ entry to tell people to stop suggesting we emit nonworking (!) code to fake real-private fields: https://github.com/Microsoft/TypeScript/wiki/FAQ#you-should-emit-classes-like-this-so-they-have-real-private-members
@RyanCavanaugh absolutely the feature of private fields is in demand. Most people are just having a hard time grasping why there needs to be such a syntactical "ugliness" when using private fields.
And - the
#
syntax is also the only technically viable solution that meets the soft private goals, except for the "no new syntax" goal. (Indeed, we were considering using it for soft private for a long while.)
For anyone else here: "using it for soft private" refers to making #
syntax sugar for Symbols - i.e. this.#foo
would access a regular property whose name is a Symbol, and the Symbol used would be unique per (containing class, name) pair. Correct me if I'm mistaken.
So if you were considering that, why did you stop? Is there a link to a discussion where the matter was ultimately decided? (tc39/proposal-private-fields#33, closed a month ago, has some discussion but not a final decision.)
@RyanCavanaugh I suspect most of the people asking for "true" private fields would be satisfied with soft-private (which is a heck of a lot more private than what TypeScript currently generates). After all, they're probably used to languages like C# or Java, but both of those languages have reflection APIs that can access private fields - which can come in handy sometimes, like for unit testing. By the same token, it's not the end of the world if JS code can introspect private fields using e.g. Object.getOwnPropertySymbols()
.
why there needs to be such a syntactical "ugliness" when using private fields
FWIW: In my experience with language design, new/unfamiliar syntax can be difficult to judge when first presented. There are some fairly objective things you can observe like "exhausting to type it out" or "looks like something else in the lang", but I think this syntax passes those 2 tests at least.
Relatedly I've noticed that it's very easy to mistake unfamiliarity with unsightliness when it comes to new syntax (I definitely do it -- I think it's pretty human). This kind of feedback is so common that I've come to expect it before proposing any new syntax.
In this particular case, having worked with this private variable syntax for some time now, it's become pretty second nature to me and I don't really find it unfamiliar anymore. As a result I do feel pretty good about this choice of syntax on that front.
@comex:
There was a long discussion about this a little ways back here: https://github.com/tc39/proposal-private-fields/issues/33
EDIT: Doh! I just now noticed you totally linked that issue in your comment :p @littledan mentions that there's no conclusion, but the following points are my understanding about how we chose to proceed (rather than stall indefinitely)
Some tldr points are:
@jeffmo I just want to clarify that I have no issues with the current proposal, I was just reflecting what I've observed other people saying about it
@jeffmo Yes, I linked to that discussion in my own post. Underscore-prefixing is unreliable and symbol property keys are ugly. Hard-private is only really necessary for capability-based security sandboxes like Caja, but (a) those are not commonly used and (b) hardening a class to be suitable for those requires a lot more attention than just using private fields; if nothing else, freeze/seal operations are needed, and it's not much more work to add a Proxy on top.
So, finally, why not reserve the # for hard private and use the "private" and "protected" keywords for soft private ? This way everybody is happy and we cover all the discussed aspects here.
@comex hard-private is beneficial for every API everywhere. Many bugs have been caused by people changing things that they pretend are private, yet someone else was depending on - hard-private helps ensure that the documented API actually matches the observable API. It's a safer default, it's a smarter API choice, and it encourages developers to make explicit choices to allow reflection and use, rather than implicit ones.
@lifaon74 About reserving keywords in class bodies: We're actually in a really good place, since if you have two space-separated identifers in a class body (which aren't static
, get
, async
, or other things like that which were explicitly added as features), it's already a syntax error. So there's nothing special that we have to do to reserve syntactic space for keywords indicating privacy in the future.
I'm not sure private
will work for soft private, though, for the same reason it doesn't work for this proposal.
@lifaon74 Beyond what @littledan says above, here's my response when this was brought up previously: I don't think the language has room for both of these. Programmers would have to learn both and constantly decide which is appropriate, even though they're solving similar problems, and most people wouldn't much care about the differences most of the time.
@ljharb I disagree with every one of the statements in your post, but it seems like more of an ideological point (on both sides) than something that can be beneficially debated, so *shrug*. But my general response would be: what kind of a dysfunctional environment leaves you unable to trust your codebase to respect wishes expressed as firmly as soft private (or private in C#/Java)? If the offending code is in a library, maybe you shouldn't be using that library; if it's using your library, well, if they want to hack up your code badly enough, you can't stop them from just editing the source.
@bakkot I have registered my opinion. I don't like #
for privates. I hope it goes my way, if it doesn't I'll still be an advocate for and user of the language. I've been a bit harsh, but I do respect the work you and the rest of the team are doing.
If you are ever in North Dallas look me up, I'll buy you a 🍺
what kind of a dysfunctional environment leaves you unable to trust your codebase to respect wishes expressed as firmly as soft private (or private in C#/Java)?
Ugh -- the adversarial web!! Perhaps you haven't noticed that on the web some code that comes in contact with your code may have been written by people whose interests are not in alignment with your own.
@RyanCavanaugh Is there a reason TypeScript doesn't use a WeakMap for private
variables?
edit: added note for performance
class Foo {
private x = 0;
increment(): number {
this.x++;
return x;
}
}
becomes
function Private() {
var map = new WeakMap(); // assuming you can polyfill for a WeakMap
return function(obj) {
var props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
var Foo = (function () {
var p = new Private();
function Foo() {
p(this).x = 0
}
Foo.prototype.increment = function () {
p(this).x++;
return p(this).x;
/*
// or, for better performance
var priv = p(this);
priv.x++;
return priv.x;
*/
};
return Foo;
})();
The same method should even support protected
variables:
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
becomes
// scoped to Person and Employee
const ptd = new Private();
class Person {
constructor(name) {
ptd(this).name = name;
}
}
// scoped to Employee
const p = new Private();
class Employee extends Person {
constructor(name, department) {
super(name);
p(this).department = department;
}
getElevatorPitch() {
return `Hello, my name is ${ptd(this).name} and I work in ${p(this).department}.`;
}
}
@bakkot
I don't think the language has room for both of these. Programmers would have to learn both and constantly decide which is appropriate, even though they're solving similar problems, and most people wouldn't much care about the differences most of the time.
This is a valid point. But if, for whatever reason, we decided in the future that we should give the option of soft private in addition to hard private, would the language be able to accommodate that? Or do we need to make a final decision now that it should be one or the other? Maybe we should think about what alternative syntax soft private might require, if private x
isn't feasible.
Personally I don't see this as a remote possibility...I think that as soon as hard private is added to the language (if this proposal moves forward) there will be a lot of desire for something a little less strict - and not just due to resistance to change, but from people who have valid use cases for reflection and would prefer not to resort to public properties with an underscore prefix. (Yes there are symbols already, but declaring properties using symbols is verbose.)
Of course soft private is not the only solution for providing reflection capabilities. One could certainly argue (and I think I agree with this) that if you want to do reflection, then in those cases you could just use protected/friend/internal (or whatever we want to offer between public and private). But this too brings us back to needing a plan for how to allow access levels other than public and hard private (even if that doesn't get implemented until a later proposal).
@mbrowne I believe it certainly is possible. You'd add a keyword in front of the #prop
declaration which would allow for an opt-in soft private behavior. For instance:
class X {
#hardPrivate = "hard private";
private #softPrivate = "soft private";
}
@pitaj Thanks.
So how do we feel about:
internal #internalToPackage
(my preference) *
Or
protected #myProtected
* meaning that I think it would be better to have internal/package access than protected and friend, not that I like the syntax. But if it's the only viable option...
@mbrowne
But if, for whatever reason, we decided in the future that we should give the option of soft private in addition to hard private, would the language be able to accommodate that? Or do we need to make a final decision now that it should be one or the other?
As I say, I don't think the language has room for both. I think we need to decide now. (Not because of syntactic restrictions, just because I don't think it's a good idea to have such similar features.)
If we have hard private, it's easy enough to allow soft private to be dealt with in userland. (The reverse is very much not the case.)
For example, you can add static reflection methods to a class (static readPrivateFoo(o) { return o.#foo; }
); given decorators this would be even easier: you could decorate a private field with @inspectable
to install said static methods, or whatever. Or, as you say, you can just use Symbols and rely on getOwnPropertySymbols
for reflection, which seem to me to be very precisely the semantics of soft private.
declaring properties using symbols is verbose
How so? Just in that you have to declare the symbol ahead of time? I know not everyone agrees, but that doesn't seem all that bad to me personally. Not enough to warrant nontrivial new syntax on its own, anyway.
But this too brings us back to needing a plan for how to allow access levels other than public and hard private (even if that doesn't get implemented until a later proposal).
I am not convinced that we need language-level support for any further accessibility modifiers. Other levels seem like they could be built without too much trouble in user code on top of the existing ones, especially given decorators. Hard private state is the only one which really requires language-level support.
@kevlened a weak map would be extremely slow compared to a regular property lookup. Visibility modifiers should not impact the performance of your code.
@bakkot So in fact we will all end up with one solution which cover only a small portion of the problem... just because the authors of the # don't want to listen and provide solid solutions ?
We already use getter/setters and symbol to achieve "private"/"protected" and "friend" and if we discuss here its because we're not satisfied with it ! It's far more verbose and slower, exposes private to public because of getters/setters and doesn't follow the DRY rule. All your examples are monkey patch to solve the problem. We already use monkey patch and we don't want them ! We aim to something native.
I could argue just like you and say : hey, we don't need # properties, we already have WeakMap... not a valid point
Moreover, reading all the comments above, most of the participants express the needs of soft private, and friend like access + inheritance. It's a majority (~2-3 for the hard vs all the remaining (10+) for soft).
@ljharb
@comex hard-private is beneficial for every API everywhere. Many bugs have been caused by people changing things that they pretend are private, yet someone else was depending on - hard-private helps ensure that the documented API actually matches the observable API. It's a safer default, it's a smarter API choice, and it encourages developers to make explicit choices to allow reflection and use, rather than implicit ones.
I strongly disagree on this one ! The bugs can only appears on the side where the developer touch the "private" fields. It's a ultra rare situation, it's its responsibility and it's fast fixed (simply refactor a variable name in most of the cases. I guess this situation is far much rarer than the need or private access (reflection) for optimization, outsourcing, etc... ! And the most critical point : even authors can't access to their own # into their package because of the class scope only. Bye bye optimizations, welcome slower code for everybody. Not all developers are gods which will use properly # properties, they will make mistakes and these mistakes will penalize potentially a lot of peoples...
@lifaon74, Symbols are a native solution for soft-private properties. They are not particularly verbose (an extra declaration whose meaning is fairly clear), they look like property access (because they are), and there's no inherent reason they should be slow (and they don't seem to be in modern engines). None of that is true when trying to use WeakMaps as a "native" solution for hard-private.
Again, I don't want to say we'll never introduce friend
, or other convenience modifiers. But right now it looks like none of them are essential (that is to say, having no reasonable solution in the language as it is) in the way that the hard-private state offered by this proposal is, and some if not all of their use cases might be met by this proposal + decorators. So they're not a part of this proposal.
@bakkot Symbols cover only an aspect of the soft private. They don't include "protected" and friendship requires to share and exports the symbols. Their are really useful, but not powerful enough. "friend" just like "protected" are essentials ! They are heavily used and allow better code by outsourcing repetitive algorithms and allow better optimization. In a world of mobile, PWA, etc... optimization is really important. So friendship should be thought now. Else developers would do monkey patch and could be stuck when native friendship will exists.
Thanks for explaining all of this, @bakkot. I appreciate your efforts to explain the issues to all the people who come to this repository. These issues have been discussed at length before, but it's possible that those conversations are hard to read, so @bakkot is being very helpful in explaining them to more people.
The questions here are complicated, and involve thinking through multiple sides. I really want to get more community engagement generally on design decisions, but reducing it to "10 people on this thread think X, and 3 think Y" is a bit simplistic. Some others have supported the design decisions in other forums, and there's only so much appetite for people to repeat their arguments over and over again. It also seems hard, in this thread, to communicate subtle issues, as @bakkot has been trying to do.
@lifaon74 I don't understand how optimization could be based on friends, protected, or even this private state proposal. The part of this proposal which is most friendly to optimization is that you declare fields--this is more optimizable than fields which are dynamically created later.
My hope is that friends, protected, soft-private, etc can be covered by the follow-on decorators proposal. It's true that this is a follow-on proposal for what is really a core capability, but I hope that a combination of symbol-named fields and methods, and whatever else people are doing now, can tide users over. The decorators proposal is not done yet, but I've been working on making it more concrete, and I have some confidence that it will not conflict with the design decisions being made in this proposal.
At this point, the proposal is at Stage 3, which means we hope the design and specification are basically done, and that the proposal is stable enough to be implemented with confidence. I'd hope that, in the future, this sort of feedback can be gathered before Stage 3. I'd appreciate any ideas to improve openness to community feedback and engagement.
@littledan A really simple example of optimization : imagine a Blob
implementation with an internal private buffer
. Without access to this buffer
, we need to use the FileReader
which is far slower. If somebody would then implements a Response
or a File
(File
inherits from Blob
) with # it's impossible to access to the buffer
to optimize internal private methods of Response
and File
can't inherits the buffer
... Simple example which strongly demonstrate why # are really incomplete and why we require friendship/protected.
Moreover, friendship is used in a lot of cases that currently are public functions/methods. In many projects, developers outsource repetitive or common core algorithm. Imagine having different classes for ArrayLike (Queue, SortedArray, etc...) which extends an abstract classes ArrayLike (all of this is abstract in JS but possible un typescript for example). All of them will share common methods and properties (including private) and could have an underlying private array
to keep data. An external sort
function could be applied to an ArrayLike and require to access private field (array
). Most of the times Helpers requires private access (so friendship or reflection).
You could take of look at the streams for example where the usage of friendship is important, and in a general manner, all the official js lib specs.
Decorators are a solution as demonstrate here but would require to modify Object.defineProperty (or kind of) and allow soft private... exactly what I point out at the first comment. Moreover "private"/"protected" keywords exists and are more elegant...
Hello everybody, after following the private class fied proposal and now this proposal, I wanted to discuss : why we don't go further.
First I need to specify i'm not a java, C++ guy or whatever, but a really involved an ecmascript lover. My motivation is to reach a consistent structure across languages heavily used and which have already implemented all the best for classes (since decades).
So for me what would be the perfect class in ecmascript :
1) attribute/method modifiers
In most of the language we find : public, private, protected and static. Currently only static is supported. For me we should use all of this words (already implemented in many languages) to keep consistency and fast code adaptation from one language to another.
The # for private sound wrong for me and the discussion (https://github.com/tc39/proposal-private-fields/issues/14) didn't convince me (people proposed concrete solutions to every problems...). Moreover the protected is still missing but extremely used in inheritance (sound strongly necessary).
Because we love to have full control of class properties in ecmascript, we could add a new attribute to descriptor when using
Object.getOwnPropertyDescriptor
orObject.defineProperty
(https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/getOwnPropertyDescriptor) :The new modifiers attribute could be an array of string representing the access of the method, and could potentially be modified after initialisation (to allow the same power than Reflect provides, allow a "backdoor" on external libraries, and allow descriptor to modify modifiers). This would bring far more power than the actual #field propose.
The public, private, protected should be allowed before static too.
To allow external classes or function to access to a protected or private member, we could use the friend keyword. Something similar to
This means than the class B can access the attribute attr of A even if it's private. We could then extends the
Object.defineProperty
:friends could be an array of friend functions or classes.
Finally for modifiers, a const keyword could be used to specify constant attributes, only allowed to be initialized into the constructor.
This will be a shortcut for
writable=false
2) Multiple inheritance
Multiple inheritance is something that a lot of developers wants (there is a lot of subject or tutorial on the web) but can only be archived through mixins or factories. I would enjoy that you won't reply : "this is too complex because of diamond structures" or whatever because this is obviously FALSE (other languages like C++ archive it easily).
The following is just an idea how to bring multiple inheritance, it's not a concrete proposal.
First of all, because of the javascript current inheritance system with prototype, we can only inherit from one class. No more.
A.prototype = Object.create(B.prototype);
or in es6class A extends B
.So we should introduce some king of new syntax.
1) We could use for example a new attribute prototypes which would be an array of mother classes, and prototype would point on the first element of this list ensuring retro-compatibility.
A.prototypes = [Object.create(B.prototype), Object.create(C.prototype)];
A extends B, C
2) instanceof should then search for all subclasses, so
new A() instanceof C
would return true.3) The super keyword will need some adjustments: I propose this king of syntax
super<B>.method
: the super class here is B. To init a class:Or we could use some C like :
super::B
. Usingsuper.method
will call the first super class (here B) for retro-compatibility.Some other fancy stuff could be added too :
super<B>()
.3) Abstract classes
Not the most important but really enjoyable, the abstract keyword could be use before the class keyword to define abstract classes. An abstract class may have abstract members and can't be initialized.
This still need more specifications.
So, the discussion is open. My purpose it to bring more in a big step to ecmascript instead of doing small steps and be stuck with retro-compatibility because of too various incremental specifications.