-
Notifications
You must be signed in to change notification settings - Fork 113
Developer experience of the syntax and semantics of this proposal #175
Comments
Hi, The strengths of JS are - among others - that it's simple and easy to learn for devs coming from C/C++, Java or PHP. So, we have syntax and classes like in some of these languages. Why not just do something like: class MyClass {
myVar = 3; // public field
public otherVar = 4; // also public field
private yetAnotherVar = 5; // private field
protected myMethod () {
// do things here...
}
} As you can see, adding access modifiers is optional in this example, the default one being const myObject = new MyClass();
myObject.myVar; // returns 3
myObject.yetAnotherVar // throws Error And while I'm at it, athough this is OT: function myFunc (String name, Number age, cityOrCode) {
// name is definitely a String
// age is definitely a Number
// cityOrCode can be any type
}
let myVar = 3; // normal variable, any type
let String otherVar = 'some string'; // can only be assigned a string type My point is: JS should be simple and not have unexpected, weird syntax. Development of the language should focus on making a developers life easier and allowing noobs to quickly understand code. In my opinion, it's better to not have a feature, than to add a weird or complicated one ( |
It would be unexpected and weird if "private" things were publicly visible properties that threw on access, exposing their existence. |
Good point. Maybe make private and protected fields not enumerable. Though I think this would only be an issue in those cases, where you attempt to add random properties to instance objects - which is bad style anyway, e. g.: let myInstance = new MyClass();
myInstance.someRandomProp = 3; // bad style, unless someRandomProp is a known public field. |
Whether it's a bad style or not isn't the point - it's a core feature of javascript that objects are mutable unless otherwise made immutable, and privacy isn't related to mutability. |
I would expect privacy to be related to mutability, just as it is the case in other languages. If the standard doesn't yet allow this to be the case, shouldn't it be adjusted first? In other words: If the standard limits you from implementing fields the easy way, why would you implement them in a hard to grasp way instead of adjusting the standard first, before attempting to implement fields? |
JS is constrained by backwards compatibility, so no, that's not something that can ever be changed. |
@nabil1337 It sounds like you might not have seen or read the FAQ about the private fields syntax. Actually this prompted me to add a disclaimer to my first post here encouraging everyone to read that FAQ as a prerequisite for commenting on this thread. This is not intended to be overly strict or unwelcoming but simply to make the discussions here more productive. This is especially important given that the private field syntax in particular has been discussed very extensively especially in the past couple years, and the syntax you suggested has already been suggested by many others and comes with some very significant tradeoffs that have been discussed many times. Of course, as you can see by reading through comments in #100, the syntax is still quite controversial even among those who are aware of the tradeoffs. But it's important to realize that syntax like this: class MyClass {
private myPrivateField = 1; // private field
someMethod() {
console.log(this.myPrivateField);
}
} ...would actually make it impossible to have fields that are 100% private. This relates to another past discussion that you might want to familiarize yourself with, regarding the desire for "hard private" and the tradeoffs between hard private and soft private: #136. And just to counterbalance what @ljharb said, in many other languages you can always access private properties via reflection, so private doesn't have to mean 100% hard private, but the committee has landed on hard private being an important requirement especially for library authors (for more on that as well as counterarguments, see #136 as well as earlier discussions in #100 and other threads). After years of careful deliberation, this class fields proposal is already in stage 3 which means it's highly unlikely that it will be changing much. The main reason for continued discussion is that there are still some alternative proposals that some community members are interested in, primarily these three:
As @littledan (a committee member) has communicated, the TC39 committee has reaffirmed consensus on the class fields proposal multiple times, so keep in mind that these alternative proposals are unlikely to succeed... but since many people still have concerns with the current proposal, there is still interest in discussing them, improving them, and advocating for them in the hope that the committee might hold off on a final decision and reconsider other options one more time. (Side-note: Personally I am more interested in advocating some smaller adjustments to the current proposal rather than rejecting it completely, and I believe the use of a prefix like |
@mbrowne Thank you for explaining! Sorry I didn't notice the FAQ. I have some remarks: The FAQ states:
It explains this as follows:
Futher:
The simplest solution would be to just throw an error when trying to access these special fields, as I proposed. My approach:
I know, this has apparently been discussed for years. But as a normal dev, you don't really get to know all these things too quickly. And please consider the largely negative feedback from the recent discussion. Shouldn't the people who actually use the language be able to decide if they want this proposal? Thanks for your time. |
Yes, it absolutely matters, because it means adding, renaming, or removing private fields becomes observable and thus part of the public api. Its contents are irrelevant, its mere existence is information that must not be obtainable. |
In addition to @ljharb's comment, it would also be really annoying from an ergonomics point of view if a private field on a base class prevented a similarly named public field or method on a subclass. Among other things, it would mean that adding a private field would always be a potentially breaking change.
A great many people involved in this discussion actually use the language quite heavily both personally and professionally, myself included. |
operationally, how many of you would actually enjoy maintaining web-projects with polymorphic-classes with subclassed-properties having the same names as [unobservable] private-fields? this feels like a low-level hammer for low-level general-purpose programming things encountered in java/c++, not a high-level glue-language like javascript used by most of us primarily for baton-passing serialized JSON-objects around (with async static-functions). |
@kaizhu256 The real problem is, one would never know what private fields are used in base class as they're not part of your project, and they're also not documented as not being part of the public API. So that whenever trying to extend an external class, your derived class would break weirdly. Even it doesn't break now (when you writes them), when the base class add a new private field suddenly your class is broken. |
@trotyl, the problem is deeper than that. i feel the whole class-based approach to solving async, UX-workflow problems commonly encountered in javascript is flawed. class-methods are ok for blocking-code design-patterns in java/c++, where adding new features simply means inserting new blocking code between-the-lines of existing blocking code. class-methods are not ok in async UX-programming where commonly added features like autocomplete, autovalidation, file-upload-progress, etc... often involve rewriting the entire ux-workflow (and any io-based class-methods with it). most of these tasks are more elegantly solved with callback-based static-functions. |
It's a bit of a stretch to say it's part of the "public api" if that weren't the author's intention at all and they indicated it with Another significant issue is that without a prefix, the interpreter wouldn't have an immediate way to distinguish between private and public at the location in the code where a field is being accessed. For a compiled language this wouldn't be a big issue, but since JS is an interpreted language, it would make all property/field access more expensive (at least at class evaluation time). @nabil1337 that's why your suggestion of just throwing an error if someone attempts to incorrectly access a private field is not as great of a solution as it seems, because the interpreter would have to check for all possible base classes and prototype modifications on every private field declaration to make sure there isn't already a public field with that name, and some of the accesses as well (at least the dynamic ones). For these reasons, even the alternative proposals that community members here are still seriously considering include dedicated syntax for accessing private fields. There is also one other option that I think is very much worth considering, which is keeping BTW @nabil1337, I'm not sure if you're already aware of this, but |
The committee members are fortunately very familiar with real-world usage and concerns, thanks to first-hand experience as @bakkot mentioned. (They have also done community outreach outside of this repo, which the committee members could speak to better than me.) As for me, I use the language every day as a frontend (and sometimes full-stack) web developer and I am not a member of the committee, just someone who noticed this github repo a while back and started participating in discussions. Despite a few lingering concerns, I think this proposal is still better overall than any alternative; unfortunately we're fairly constrained by the existing language in some ways and no solution is going to be perfect. There are plenty of other members of the community who agree. However, they're not well-represented here because those who are satisfied with the proposal have less motivation to comment. As to the great number of people who continue to oppose the |
Thanks for all the replies.
It's not part of the api if it's private. Knowing about the existence of a private property that you cannot access doesn't really matter in day to day programming.
That's an important point. If in a child class, it must be possible to define a field or method by the same name. If we look at PHP (by far not my favorite langauge, but widespread), we are allowed to have this code: class ParentClass {
private $field = 1;
public function print () {
echo $this->field;
}
}
class ChildClass extends ParentClass {
public $field = 3;
}
$child = new ChildClass();
echo $child->print(); // prints 1
echo $child->field; // prints 3 This is intuitive, because the parent method can fulfill the author's intent without being misled by whatever the child classes' field value is.
Not quite sure if I understand you here. Isn't this a one time scan that could easily be stored in memory?
That's why I pointed it out as a disadvantage. Because it's inconsistent: You can do
I agree. I might not be aware of all consequences yet, this is why I'm hoping to learn. But it's not easy to find the right place to start. Information seems to be scattered to serveral issues and text files. One more question I couldn't find an answer to: What about protected fields and methods? |
I don't have personal experience with the inner workings of the JS engine (aside from outwardly observable behavior) so there are others who would be better qualified to describe this in detail. But I do know that unprefixed private fields (sharing the same namespace as public fields) would complicate the current semantics and add some amount of additional overhead, whereas the current proposal would keep the current semantics for public property lookups unchanged. Yes, the field declarations at the top of the class could be scanned once and stored in memory, but there are more significant differences when it comes to the places (call sites) where fields are accessed.
If public and private shared the same namespace, then I don't see how such dynamic access could work in all cases without requiring extra checks for private fields at run-time. Anyway, all of this is moot given that the ability to detect the presence of a private field from outside a class has been deemed unacceptable by the committee. Truly hard private fields are apparently one of the most frequently and emphatically requested features from library and framework authors (for reasons discussed in many previous threads, although I'm sure someone could summarize them if you can't find them easily). |
Here's a link to a fairly recent issue specifically about More recent discussion: My opinion: But most of all we need to make sure we're not painting ourselves into a corner with this proposal: On the other hand, maybe we're not painting ourselves into a corner but just limiting ourselves a bit (but I'm not fully convinced): |
For completeness I'm also including a link to a very relevant comment by @bakkot, although I remain skeptical that private fields + decorators will be a fully acceptable solution for intermediate access levels in the long term: |
The only reason why hard private is desired over soft private I can find is this from FAQ:
Honestly, it only explains why encapsulation is required. I don't see why you need to hide all those private members. You can always find out what are those via reading the source code anyways, even if there is hard private. In the reality, you only want to prevent users from "depending upon" those undocumented APIs. To me, just disallowing developers from accessing them is more than enough. (I actually prefer TypeScript style compile-time encapsulation without runtime enforcement. Private should just be an advice instead of limitation. Personal opinion though.) |
@SCLeoX it's easy - and more common than you'd think - for users to write code that branches on the mere presence of public properties as a brittle means of feature/version detection. For those of us that want to truly avoid breaking changes in a non-major version, preventing access is not nearly enough. |
@ljharb I don't get it. Even if the "hidden change" requires a password to unlock, you can always .toString to get the source code of the function. |
@SCLeoX the point of private implementation details is that the maintainer is/should be free to change/refactor them in any way they like that doesn't change the observable behavior - the API.
|
@ljharb If no observable behavior is changed, why there is a change in the first place... If there is a change in the code, something should change in its behavior - let it be performance improvement or bug fixes. If the behavior is exactly identical, for example, renaming a local variable, there should not be a new release of that library anyways. |
@SCLeoX What if the library author renamed or removed a private field in the process of reimplementing something to fix a bug, but otherwise the public API remained unchanged? |
A solution that could be technically viable but probably not practically viable would be to use
I think that makes more sense semantically than the other way around (i.e. it makes more sense than a decorator that would somehow turn a symbol into a hard private field). The unfortunate part is that class authors don't always anticipate the need for reflection. So even in cases where reflection would be very useful and done with the full expectation that the author could change the internal implementation at any moment, it simply wouldn't be an option unless the class author decorated those private fields. This is worsened by the fact that the decorators proposal was recently changed so that you can no longer access private fields via a decorator on the whole class (due to concerns about leakage of encapsulation), but would have to decorate each field even if your intent is just to make all fields available for reflection. In some ways I think it would be better if soft private were the default, but OTOH, exposing internal implementation details that some developers might not even realize they're exposing is arguably worse, so hard private is a better default from that perspective. Decorating each field to enable reflection could be a bit tedious especially for classes within your own code base that will never be consumed by a 3rd party, but I haven't seen many use cases for reflection of private state within the same code base in the first place. |
how many js-devs are naive enough to believe it's practical to audit webapps with hard-privates/encapsulation to guarantee against data-leakage when mixed with 3rd-party code? nobody. security-arguments for private-fields in javascript are laughably impractical. |
Plenty are, and plenty do it, myself and my company included. I’m sorry your experience is with devs that don’t, but that doesn’t mean your experience is universal nor does it give you the right to be condescending. |
@ljharb nobody mixes crypto, credit-card, password, etc. with untrusted javascript code regardless if it's encapsulated or not. one does not see untrusted-code/ads running on merchant's payment webpage, and private-fields is not going to change that. sensitive data has and will always be handled in javascript by running it in isolated webpage (client-side), or isolated at network-interface level (server-side). but never at code-level. |
Quotes with my own opinion from that article:
And In lieu of conclusion part of the article. Everything else is facts without judgement and interpretation, just facts. @slikts (or @mbrowne) are you able to prove the opposite? If yes, please go ahead and create your article (or just a comment here in github), which shows logic flaws or false facts in my review. If not, you have to confess that my article is more neutral than FAQ, README and presentation made by committee till this moment, because it doesn't hide issues under the carpet. |
I don't want to blame or offend anybody, but I think that supporters of existing proposal see my article as not impartial, because while reading they feel proposal becoming not as good as they felt it before. And the easiest explanation is that author isn't neutral and not that biased reader had wrong assumptions or missed something important during initial evaluation of current proposal. |
This just isn't serious. The article is blatantly not neutral; it gives your preferred interpretation even in the title. There can't be a discussion if the basic facts can't be agreed upon. The relevance of neutrality is that it's a starting precondition for reliable polling, but just starting; beyond that there's complex questions like how to control for self-selection bias, etc. Bringing up the FAQ and readme is just deflection.
Having to include a disclaimer like this should have been taken as a clue that it's not appropriate. |
@Igmat I agree that the readme of this repo is not neutral, but I don't think it has any obligation to be. Do you think every TC39 proposal should be required to include a long list of downsides and potential downsides in its readme rather than simply stating the purpose of the proposal and making a case for it? As to the documentation in this repo in general (not just the readme), could it be improved by better documenting tradeoffs and drawbacks of the proposal? Certainly. But again, I don't think it has an actual obligation to do so. If the community were to put together a list of tradeoffs that fairly covered both sides of the argument and posted it somewhere, then it would be reasonable to request that there be a link to it in the readme, but that's about it. Expecting a full documentation of not only the pros but also the cons of the proposal is simply asking too much of the committee, and the community already has plenty of opportunity to bring up problems and concerns in these discussion threads. And finally, just because the documentation of this repo isn't as impartial as it could be doesn't mean that when conducting a survey you should focus more on the counterargument. Regardless of whether the documentation here were neutral or completely biased, the correct way to do research is to just stick to the facts and/or present both sides as neutrally as possible. |
@slikts, @mbrowne remove three paragraphs I mention before as my own opinion and add But I'm not trying to convince anybody that my research is 100% correct, even though I doubt that such change to the article will significantly affect survey results.
I just fulfilled the gap in argumentation of opponents of existing proposal. So what do we have now?
|
The community feedback of the proposal continues to show I fail to see why Private Symbols are not adopted in place of the sigil syntax, for 3 main reasons:
To show what I mean by greater flexibility, something that is not possible with the sigil syntax.
Yes, I think they should be required to include drawbacks, and/or alternatives sections. Many RFC processes already do this. It seems like the FAQ attempts to cover this, but ends up being one sided, by only selectively showing alternatives that fail. IMO this is even worse, because it falsely props up the proposal. |
I agree that we should have an open discussion about pros and cons of proposals, and would appreciate your help in collecting this in general. For future proposals, let's try to collect this input earlier in the process, prior to Stage 3, so that it's easiest for TC39 to take it into account. At some point, though, a conclusion should be drawn, and a rationale published. |
@littledan Does this mean that the committee is working on a rationale document explaining the decisions that were made on this proposal? I think we have some sense of individual arguments in favor of certain points, e.g. hard privacy and the syntax, but what's missing is the big picture, particularly why this proposal was chosen instead of alternatives, for example:
(I can imagine a rationale document that would make a strong case for the current proposal over these alternatives. It wouldn't satisfy everyone of course, but I think it would help explain the decisions in a way that the meeting notes currently don't do adequately.) |
I meant, we should make such comparison/rationale documents earlier in the process of future proposals. For proposals at later stages of development, like this one, an FAQ explaining the selected option is probably the more relevant thing. If anyone wants to write either form of documentation for this proposal, I would be happy to review it and include it in this repository. Right now, I'm most excited about @neilkakkar 's effort to document this proposal in MDN, and other efforts to help JavaScript developers use the feature, rather than explain the rationale. |
Ok, it could be in an FAQ format; the important thing is "explaining the selected option". But I don't see how a non-committee member could write the first draft...plenty of details can be gleaned from public statements by committee members, but the big picture...? (For the syntax, the private fields FAQ would be an excellent starting point, but the syntax is only one aspect.) Maybe a gist from the committee with some bullet points could provide the starting point, and community members who are familiar with the issues (hard vs soft privacy, Set vs Define, etc.) could fill in the details including a glossary of terms.
Given that the committee has already reached consensus on this proposal, it does make sense for this to be the first priority, yes. |
You're right, it doesn't need to be in FAQ form. Happy to answer any questions you have; I would recommend starting with the past presentations and notes linked from the readme if you are interested in history beyond the rationale we have been discussing in these threads. |
Yes, it's a good idea to ask questions. If I know about FAQs beforehand, I can pre-emptively include them in the documentation. |
I thought @littledan's most recent comment was in reference to the rationale for choosing this proposal over alternatives. As to usage, personally I'm pretty clear on it. But I do have an idea for a usage FAQ question... Question: How do I access fields defined in a subclass from a parent class's constructor? |
Happy to discuss both kinds of questions. |
Thank you @littledan! :) I started a new thread for this: |
Catching up on this long thread. Were non-React use cases for public fields ever posted? They still seem redundant to me if you are defining a constructor, and if you are not defining a constructor then using a class over a plain object seems unusual. Would like to understand more. |
@MichaelTheriot Yes, for example, see the example in the explainer of this repository, which is based on custom elements and does not use React. |
If we are talking about the same example it appears to suffer from the same issues I mentioned. |
@MichaelTheriot for a recent, detailed discussion of this issue, see rdking/proposal-class-members#1 and the continuation of that discussion in rdking/proposal-class-members#3 |
Also #142 (comment), and #100 (comment) |
If you’re not defining a constructor but are inheriting from another class - one that has an API that reads from instance properties - then you couldn’t use a plain object (or instanceof would break). React is just one example of this pattern; Backbone is another, and I’m sure there’s many current ones too that i don’t have available off the top of my head. |
Sidenote, as an end user of "open standard" JS and Web APIs, I feel that our developer experience may not always valued enough. The discontent summarized in #100 is another example. |
Not true, since that plain object can just do: let myobj = {
somefields: somevalue,
__proto__: ExpectedClass.prototype
}; This is still a fundamentally prototype-based language. |
@rdking you’re correct of course, but “plain object” tends to mean one that inherits directly from Object.prototype; you’re describing an object literal. |
Fair enough. I was thinking of "plain object" in terms of an object not created by any kind of factory function or |
Assuming that private properties are not enumerable, if non-public—even non existing ones—properties throw, it virtually seals the class—i.e. preventing any additions. |
Yes, it would be an unacceptable concession; JS objects are extensible by default, and making "adding the first private field" be the equivalent of adding |
@ljharb of course that would be accompanied with a way to reflect that state (a property boolean or predicate function). |
This new issue is intended as a continuation of the recent discussion in #100. (The only reason for a new thread is that the original has become very, very long, which among other things makes it less approachable to anyone not already participating in the discussion.)
VERY IMPORTANT NOTE: Anyone who would like to comment here should first read the PRIVATE SYNTAX FAQ. This will save everyone time. Thank you.
The text was updated successfully, but these errors were encountered: