tc39 / proposal-class-fields

Orthogonally-informed combination of public and private fields proposals
https://arai-a.github.io/ecma262-compare/?pr=1668
1.72k stars 113 forks source link

Developer experience of the syntax and semantics of this proposal #175

Open mbrowne opened 5 years ago

mbrowne commented 5 years ago

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.

nabil1337 commented 5 years ago

Hi, I've been programming with JS for about six years now. I never intended to influence the standard, because most new things were indeed quite useful and made sense. But I have a feeling that the language is now being bloated with too many and partially weird things, just like the # char to declare private fields.

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 public. Public fields will simply be properties on the instance object that can be accessed normally from outside the class, whereas attempting to access private/protected from outside will throw an error e. g.:

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 (Object.assign, # for private, ...)

ljharb commented 5 years ago

It would be unexpected and weird if "private" things were publicly visible properties that threw on access, exposing their existence.

nabil1337 commented 5 years ago

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.
ljharb commented 5 years ago

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.

nabil1337 commented 5 years ago

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?

ljharb commented 5 years ago

JS is constrained by backwards compatibility, so no, that's not something that can ever be changed.

mbrowne commented 5 years ago

@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 # is the best possible option for private fields given the constraints. But the above summary is not a reflection of my opinion but rather my attempt at a realistic description of the situation right now, so as not to give anyone unrealistic expectations.)

nabil1337 commented 5 years ago

@mbrowne Thank you for explaining! Sorry I didn't notice the FAQ.

I have some remarks:

The FAQ states:

Having a private field named x must not prevent there from being a public field named x, so accessing a private field can't just be a normal lookup.

It explains this as follows:

If private fields conflicted with public fields, it would break encapsulation; see below.

Futher:

Library authors have found that their users will start to depend on any exposed part of their interface, even undocumented parts.

The simplest solution would be to just throw an error when trying to access these special fields, as I proposed. It doesn't really matter if someone knows that there is a private field foo, as long as they are unable to get it's contents. This is probably also what #136 meant.

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.

ljharb commented 5 years ago

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.

bakkot commented 5 years ago

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.

Shouldn't the people who actually use the language be able to decide if they want this proposal?

A great many people involved in this discussion actually use the language quite heavily both personally and professionally, myself included.

kaizhu256 commented 5 years ago

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).

trotyl commented 5 years ago

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?

@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.

kaizhu256 commented 5 years ago

@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.

mbrowne commented 5 years ago

@ljharb

Yes, it absolutely matters, because it means adding, renaming, or removing private fields becomes observable and thus part of the public api

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 private, but this is indeed an important consideration for anyone writing classes that will primarily be consumed by others. I found the code example in this comment by @bakkot quite helpful for understanding this issue.

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 # but adding the keyword private to the declarations (e.g. class MyClass { private #x = 1; ... }). That would make the syntax more consistent and easier to explain in the future if other access levels besides public and private were ever added.

BTW @nabil1337, I'm not sure if you're already aware of this, but this['#x'] is not a valid way of accessing a private field in this proposal. That would access a public property named '#x'. This is an unfortunate but necessary consequence of the rest of this proposal given the need for backward compatibility.

mbrowne commented 5 years ago

@nabil1337

Shouldn't the people who actually use the language be able to decide if they want this proposal?

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 # syntax, it's unclear how many of them have fully looked into the consequences of alternative options such as the syntax you suggested. Certainly some people have fully considered the issue and still object to #, but it's important to learn about the tradeoffs that come with each of the alternatives.

nabil1337 commented 5 years ago

Thanks for all the replies.

@ljharb

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.

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.

@bakkot

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.

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.

@mbrowne

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).

Not quite sure if I understand you here. Isn't this a one time scan that could easily be stored in memory?

I'm not sure if you're already aware of this, but this['#x'] is not a valid way of accessing a private field in this proposal.

That's why I pointed it out as a disadvantage. Because it's inconsistent: You can do this.#x but not this['#x']. But then you can do both this.x and this['x'].

As to the great number of people who continue to oppose the # syntax, it's unclear how many of them have fully looked into the consequences of alternative options such as the syntax you suggested. Certainly some people have fully considered the issue and still object to #, but it's important to learn about the tradeoffs that come with each of the alternatives.

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?

mbrowne commented 5 years ago

Not quite sure if I understand you here. Isn't this a one time scan that could easily be stored in memory?

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. # immediately signals a private field meaning public and private could be distinguished more efficiently. Another thing: when I mentioned "dynamic" lookups I was referring to something like this:

obj[myPropertyName]

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).

mbrowne commented 5 years ago

One more question I couldn't find an answer to: What about protected fields and methods?

Here's a link to a fairly recent issue specifically about protected: https://github.com/tc39/proposal-class-fields/issues/122

More recent discussion: https://github.com/rdking/proposal-class-members/issues/3#issuecomment-436042432 (note: external link) https://github.com/tc39/proposal-class-fields/issues/150#issuecomment-439663963

My opinion: First of all, I agree with a point that some committee members have made in the past: even if protected were viable for JS (which is questionable given the lack of strict typing), access modifiers should be based on real-world usage and feedback since JS is unlike other OOP languages in important ways. It would be really good to be able to work with private fields for a while to figure it out and ensure a good decision—including whether or not intermediate access modifiers like protected are actually needed in the first place. The use of decorators (currently in stage 2) in combination with private fields gives a lot of interesting possibilities for sharing and enabling reflection on private fields. Perhaps private fields + decorators will be sufficient, but if not they can still help provide very useful feedback that can inform future proposals. I also believe that some sort of module-scoped access level for fields (could be called internal) might be more generally useful and appropriate for JS than protected (friend could also be useful), but regardless, there are important use cases for multiple classes sharing private members internally.

But most of all we need to make sure we're not painting ourselves into a corner with this proposal: https://github.com/tc39/proposal-class-fields/issues/144#issuecomment-430585551

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): https://github.com/tc39/proposal-class-fields/issues/122#issuecomment-427630234

mbrowne commented 5 years ago

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: https://github.com/tc39/proposal-class-fields/issues/144#issuecomment-430701900

SCLeoX commented 5 years ago

The only reason why hard private is desired over soft private I can find is this from FAQ:

Library authors have found that their users will start to depend on any exposed part of their interface, even undocumented parts. They do not generally consider themselves free to break their user's pages and applications just because those users were depending upon some part of the library's interface which the library author did not intend them to depend upon. As a consequence, they would like to have hard private state to be able to hide implementation details in a more complete way

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.)

ljharb commented 5 years ago

@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.

SCLeoX commented 5 years ago

@ljharb I don't get it. ._. So what's the problem with feature/version detection... If there is a change, there is a way to detect it. Otherwise, it will be the exact same thing. ._.

Even if the "hidden change" requires a password to unlock, you can always .toString to get the source code of the function.

ljharb commented 5 years ago

@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.

.toString is a separate issue, that can be handled by using .bind or Proxy, but in practice, isn't really a concern.

SCLeoX commented 5 years ago

@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.

mbrowne commented 5 years ago

@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?

mbrowne commented 5 years ago

A solution that could be technically viable but probably not practically viable would be to use private x for soft private (which would translate to symbols or something similar) and #x for hard private. The problem is that having two kinds of native syntax for private could be confusing, and not everyone agrees on which is the better initial choice when writing a class that will be consumed by others. With hard private fields, you'll be able to use a decorator to expose them, e.g.:

@reflect  // an example decorator that would make `#x` available to a reflection API
#x

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.

kaizhu256 commented 5 years ago

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.

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.

ljharb commented 5 years ago

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.

kaizhu256 commented 5 years ago

@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.

ljharb commented 5 years ago

Yes, they do, and yes one does, and yes, it is. Again, your perspective may not include these things, but that’s a limit of your perspective, not of the language.

mbrowne commented 5 years ago

@kaizhu256 I wasn't referring to security. If some devs want to use this feature for security reasons after doing a rigorous examination of possible loopholes and taking measures to prevent them, that could be a side benefit. But the purpose of access modifiers is to hide implementation details, not necessarily to make access impossible (as evidenced by other languages).

kaizhu256 commented 5 years ago

@ljharb we agree to disagree. i don't believe it's feasible to audit real-world apps using private-fields to guarantee against data-breach when run with untrusted javascript code. i certainly wouldn't trust my job with it.

zenparsing commented 5 years ago

Thanks @nabil1337 for sharing your point of view here.

I think that your comments get to the heart of the problem: by using the "loan word" class we've created very strong expectations around access modifiers. These expectations are reinforced by TypeScript, where the traditional access modifiers make perfect sense.

The hash-syntax and hard privacy design attempts to swim against the current of all of those expectations. Furthermore, my experience (and the experiences of others here) seems to indicate that lack of syntax for hard/non-reflective privacy is simply not a pain-point for a large population of JS users.

One of the early goals with private state was to provide a syntactic way of explaining the behavior of built-ins and their "internal slots." But there are other ways to serve that niche use case.

mbrowne commented 5 years ago

@zenparsing Does this mean that your classes 1.1 proposal isn't what you ideally wanted, but rather a compromise in an attempt to satisfy those who insist on hard privacy?

ljharb commented 5 years ago

I agree that part of the problem is the word “class” and the assumptions it invites from other languages, and i dearly wish a different keyword was used in ES6.

However, i don’t agree that we should be constrained by those expectations - rather i think we should instead set better ones.

nabil1337 commented 5 years ago

Thanks @mbrowne

# immediately signals a private field meaning public and private could be distinguished more efficiently.

That's reasonable. Then again, other languages have to deal with similar restraints.

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.

If you cannot access it and it doesn't restrict you (see my PHP example), it just doesn't matter that much. We have to acknowledge: Other languages have already solved this problem. Doing something completely different makes no sense, since it's obvious that a detectable private field that cannot be accessed is just not able to offer potential for circumventing apis. For someone trying to rely on that, the provided api has to be really, really bad. But then the problem is the original author, not the one misusing the error throwing of a private property.

since JS is unlike other OOP languages in important ways.

The major difference is that it's prototype based. But since the keyword class was introduced and the semantics suggest you deal with a language remotely resembling PHP or Java, it's just natural do go with the flow and not do something completely strange. We already have extends and super. The missing ones are private, protected and public! Even TypeScript has them.

@SCLeoX

Honestly, it only explains why encapsulation is required. I don't see why you need to hide all those private members.

Thanks! That's what I'm talking about! It would be acceptable to assume that making private fields undetectable is actually necessary, if there was experience in other languages hinting towards a more strict solution. But as we can see in PHP, it's not a problem. Day to day problems by having an unaccessible private field just don't exist. I mean, we don't even demand a reflection api here, which would give room to some concern regarding the described issue.

@ljharb

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.

But we are talking about private properties that throw upon trying to access them. Not about public ones. If you'd rely on those private properties throwing errors, you'd very well know your code is... no that good. The real issue behind this seems to be, that the library author doesn't offer a good api.

Hi @zenparsing

I think that your comments get to the heart of the problem: by using the "loan word" class we've created very strong expectations around access modifiers. These expectations are reinforced by TypeScript, where the traditional access modifiers make perfect sense.

Yup. For me, the class keyword is not a problem but an opportunity to get closer to classical OOP languages and attract more JS programmers. If we make JS more similar to Java and PHP, it will enlargen our programmer count. If we move away from these by implementing highly unusual, untested solutions, we require additional intellectual work, thus making it harder for newbies who might know some Java or PHP to fully understand JS. We'll also move away from TypeScript syntax, speading even more confusion between the languages.

mbrowne commented 5 years ago

Then again, other languages have to deal with similar restraints.

What languages are you thinking of? All the ones I can think of with access modifiers are either compiled in advance or have a type system that is evaluated very differently than JS. Of course, this is still a solvable problem if the slight performance impact is worth it; I see the performance and semantic simplicity of property lookups in this proposal as one of many points in its favor rather than an essential requirement.

Even TypeScript has them.

You have a point regarding syntax expectations, but again, TypeScript is different because of the compilation step.

SCLeoX commented 5 years ago

@mbrowne

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?

That's exactly why soft private is enough. Soft private can already prevent developers from using those private fields. (Just to be clear, soft private = inaccessible but detectable)

ljharb commented 5 years ago

@SCLeoX again, it’s not enough to prevent developers from branching on those fields’ presence or absence. Either it’s fully “hard private” - inaccessible and unobservable - or else it’s fully public.

mbrowne commented 5 years ago

@ljharb I think it would help the discussions here if you noted that you're using the word "public" in a very untraditional way. In fact even if we take the word "public" completely literally according to its English language meaning without any reference to its usage in programming, there's still some ambiguity.

SCLeoX commented 5 years ago

@ljharb again, it's not enough to prevent developers from branching on other things that are changed even with hard private.

ljharb commented 5 years ago

@mbrowne sure, what i denote as “public” is anything i can observe or access at runtime (barring function toString). @SCLeoX it’s fine if they branch on the presence or absence of a bug - that’s public behavior. It’s not fine if they branch on the variable names i used, for example.

SCLeoX commented 5 years ago

@ljharb First, if a programmer has to branch according to a variable name in the library, there has to be something wrong with either this programmer or the creator of the library. Usually, it is the later case - they probably introduced a breaking change without bumping major version number so the user had to detect version in such weird way. However, even though it may sound ridiculous, it cannot be helped. Sometimes library authors just refuse to change or disappear altogether. Of course, one can use a local copy instead, but that is actually much worse since doing so prevents it from receiving any future security patches that may come.

Second, even if those fields become hard private, there are still other more unreliable, error prone, and disgusting ways to achieve the same goal. As I said before, if there is a change in variable name, there has to be change in the public interface - otherwise there should not be a release. If there is a change in the public behavior, there is a way to detect it. Even if the change is merely performance improvement - you can still detect it via running benchmarks. As I said, it is much more unreliable, error prone, and disgusting. However, please note that, if the change in public behavior is so slight and hard to detect, there should not be a demand of branching in the first place.

Third, there are still other ways to get the name of hard private members. I previously suggested using ".toString". Yes, you can hide the source code of your function with bind or proxy magic, but I have never seen a library that intentionally does that in order to hide the source code. Also, please note that even if ".toString" is blocked, there are still other ways of getting the source code. If it is in the browser, you can simply launch a Ajax request to get the source code of the library or if it is in Node, you can always fs.readFile. The point is, introducing hard private just to hide variable names only makes it a little bit harder and encourages using bad code. It doesn't fix the "problem".

hax commented 5 years ago

Actually, I agree most points from @ljharb and @bakkot about privacy. But unfortunately, I hope you (and all TC39 members support current proposal) also apply same standards to public fields.

@ljharb said

the maintainer is/should be free to change/refactor them in any way they like that doesn't change the observable behavior

Obviously current [[Define]] semantic of public fields just break this requirement.

Actually, @ljharb also has a use case of iterate all own properties outside of the class (to support public field idea), but as I mentioned, this requirement is inevitable failed if introducing private, because in practice, many own properties are used for store internal state (for example, prefixed by _), and if using #foo replace _foo (which will be the consequence of the slogan: "#" is the new "_"), then you just break the iteration use case. The motivation of private fields (encapsulation) and the (at least some of) motivation of public fields (iterating own props outside of class) are essentially collided , and the duality of syntax/term of them just make the things worse.

ljharb commented 5 years ago

@hax it does mean that those who have relied on _ being "good enough" will have to ship a single breaking change in order to make all those things truly private, yes - i don't see how that poses a conflict.

hax commented 5 years ago

@ljharb If such breaking change is ok, then the breaking change of using accessors replace own properties should be ok too --- Both breaking changes are due to wrong assumption of enumerable own properties are API exposed by class author (actually they are just general reflection functionality provided by the language).

Igmat commented 5 years ago

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.

@mbrowne I can't agree with this statement. I don't know where did you find developers who ARE AWARE of all disadvantages of existing proposal and still agree that it should land as is. Even though, I understand, that after presentation of this proposal and answering to questions like why # sigil instead of private keyword? using FAQ majority of them will agree with it. Such thing caused by ONLY ONE reason: they DON'T KNOW the cost of this proposal. And it's high. When you'll try to present this proposal INCLUDING all its trade-offs, you'll find that majority is AGAINST of landing it as is. Just check numbers in poll I made (#162) in Russian speaking community, my article represents all sides of current proposal (not only good ones), and I guess that numbers clearly shows how negative feedback will be after encountering some of listed issues by average developer.

As to the great number of people who continue to oppose the # syntax, it's unclear how many of them have fully looked into the consequences of alternative options such as the syntax you suggested.

I looked - and Symbol.private has less number of issues than existing one. The major one was impossibility to implement Membrane pattern with Proxy transparency, but as I shown in https://github.com/tc39/proposal-class-fields/issues/149#issuecomment-441057730, it is easy solvable problem, and just requires a little bit of additional care when Membrane faces private symbols. Second issues - is lack of shorthand syntax, but it's only due to lack of attention to this proposal, since I've suggested few ideas for shorthand syntax on top of if (#134 and #149) and already have one more idea to show. But, obviously, we can't choose some of them or find good new shorthand syntax, since base proposal didn't land even stage 1.

mbrowne commented 5 years ago

@Igmat You make some good points, and actually I made a very similar argument about [[Set]] vs. [[Define]]. I think that most developers would be opposed to the [[Define]] semantics in this proposal and the only reason we haven't heard more concerns from the community about it is that they are unaware of it in the first place. And as for those who are aware and think [[Define]] is OK or preferable, I suspect that some (but not all) aren't aware of the full ramifications.

Anyway, my comment was more about initial reactions to the syntax, and how those reactions inspired a lot of people to object to it, whereas those who learned about the reasons for the syntax and thought it was reasonable (and were able to get over aesthetic reactions like considering it "ugly" or "weird") haven't been as active here. With the exception of [[Set]] vs. [[Define]] (and maybe not even that since it's not exactly the end of the world), I doubt that a majority of developers would find the tradeoffs of this proposal unacceptable... But that's just my opinion; we don't really know. I think it's great that you took the initiative of conducting a survey, but even if your article had been written in a far more impartial way than it was, I don't think informal surveys tend to produce deeply considered and unbiased responses. I do wish that we had more reliable data on which tradeoffs are most important to the community. But—sorry to be pessimistic—I don't know if it would make much difference at this late stage anyhow.

mbrowne commented 5 years ago

@ljharb It would be possible to use the private symbols proposal to cover the use cases where hard private is absolutely needed, and let private fields be soft private. I'm wondering, why did you (and why did the wider committee, if you know), reject this option?

ljharb commented 5 years ago

@mbrowne private symbols, i believe, would be hard private, not soft, and if so that’d be fine with me - but I’m told they have issues with membranes, and as such are not an option.