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

Can we take a step back? #144

Closed rdking closed 5 years ago

rdking commented 6 years ago

When there is so much feedback on this proposal from those who are aware and choose to speak at all and from members of the TC39 board itself that there are several things wrong with this proposal that make it a less than adequate solution to the problem of adding data privacy to ES, isn't that a good enough indication that it may be time to step back and re-think?

How about, instead of trying to push through with class-fields, we come up with "class properties" instead? The general idea is simple. Objects have properties. Functions are objects. Classes are functions with a prototype object. Therefore classes should support declaring properties for both its function and its prototype.

Can we solve and add this to the language first, separately from all other concerns? No private, no protected or other intermediate. Just ordinary public and static properties. I'm beginning to come to the impression that until this much is solved, the path forward for a reasonably acceptable, non-future path blocking proposal won't easily reveal itself. Maybe we're trying to take too big of a jump all at once.

hax commented 6 years ago

@ljharb

in actual usage in all the browsers they are much slower, whether they're "inlined" or not.

You should ask engine guys, or just test it yourself: https://jsperf.com/data-vs-accessor-vs-getter-setter/15

hax commented 6 years ago

@ljharb

It's immaterial because whether you agree with it or not, empathy would require supporting the use case of folks who find getters/setters unacceptable.

Such logic is bad. It basically mean anyone could ask for any feature because they can find your solution unacceptable by any reason.

ljharb commented 6 years ago

@hax you’ve made many personal attacks against me and continue to do so. Please stop.

hax commented 6 years ago

@mbrowne

what is the point of using a private property and getter/setter in the case where it's just behaving the same way as a public property? I don't see how it provides encapsulation in any way

For the developers who really believe the importance of encapsulation and want private feature, they should agree the best practice is:

  1. private by default
  2. only add public getter when you need to expose the value to outside (still keep immutability)
  3. only add public setter when you really need to expose the ability of changing state from outside

Provide public property definition like current proposal just make it too easy to break the best practice. Note JS community never have private state before, and be habituated to use public property without full thought.

Even someone think private may good, they could use public property anyway just because of dislike of #, or to avoid extra # for all this.#x.

That is another reason why Classes 1.1 is a good proposal. Because the shorthand syntax of instance variable is very ergonomics (could save many this.) and give you shorter and cleaner code, so the developers would be happy start from all instance variables and only expose them by accessor when really needed. So such a proposal encourage the adoption of private feature and lead the community to the world of better encapsulation.

hax commented 6 years ago

@ljharb

you’ve made many personal attacks against me and continue to do so. Please stop.

Which one you think is personal attack? It may just because my English is too bad, so I'm sorry and I would like to take it back.

ljharb commented 6 years ago

@hax commenting on my apparent lack of empathy and “sick” opinions, to name a few. I’m glad to hear they weren’t intentional.

mbrowne commented 6 years ago

@hax

For the developers who really believe the importance of encapsulation and want private feature, they should agree the best practice is:

  1. private by default

Too late. If we were designing a new language I would agree with you, but JS has never had public properties and doesn't even use a public keyword for its public properties, so making public the default is much more consistent with the mental model of the language. Also, # is very short so you'd have to be really lazy not to use it even if you believe it's better. Personally I share the philosophy of private by default and write my classes that way (using private in TypeScript for example), but especially given its history and user base, I think the language should also be accommodating to other styles. For example, people coming from Python might prefer to just stick with underscore prefixed properties.

And as I pointed out earlier, people are already using frameworks that depend on creating public properties...for example, one could argue that state in React should be read-only from the outside, but as of now it's public and it will be a great convenience to React developers to have a public property declaration syntax.

  1. only add public getter when you need to expose the value to outside (still keep immutability)
  2. only add public setter when you really need to expose the ability of changing state from outside

These are good rules and this proposal does nothing that prevents you from following them. I get your point and I wish that JS had a policy of "private by default" from the beginning, but given that the current state of affairs is "public by default", I disagree that the language should force developers into this. Given the current proposal, if you start out with a public property because it's a mutable property that you want to be accessible from the outside, and then need to change the implementation later, you can refactor without breaking any external code. That's a great thing, and something that's not possible to do in Java or C#. The inability to refactor in that way is really the only reason why otherwise "public" properties in Java/C# should be implemented using getters and setters from the start.

hax commented 6 years ago

This is off topic, but I must respond to @ljharb .

@hax commenting on my apparent lack of empathy and “sick” opinions, to name a few. I’m glad to hear they weren’t intentional.

About empathy, I remember each time I said I think you short of empathy to normal programmers, I had given some explanation in concrete cases for how I believe the normal programmers think. If you think my explanation is invalid, we could discuss it in related thread. If I really didn't give any explanation in concrete cases, I would be glad to apologize for that as I agree such word alone is unconstructive.

But, on the other side, I hope you don't treat anyone saying you short of empathy (I remember in some threads others also gave you the similar comments) as "personal attack". Actually, I think you are a very clever guy, have a full understanding of the complex JS spec! I guess you are just too clever to see the pain of average programmers, and treat many issues in a more spec writer way. Actually we see many most clever guys in different areas all have the similar characters.

About "sick", my original words is "Such logic is sick. It basically mean anyone could ask for any feature because they can find your solution unacceptable by any reason."

I didn't say one's opinion is sick. But maybe "sick" is also a wrong word for "logic". What I really mean should be: such logic is bad because it nullify all disagreement by arbitrary "unacceptable". I deleted the "sick" word in original comment.

@ljharb I know we have many disagreement in many discussions, but you should believe actually we also have some agreement. But if I agree what you said, I don't write "Ok, I think you are right!" because it doesn't provide any new information. Sometimes I just use 👍 reaction but you may not notice that. So it looks like I always against you. No, it's not true. Even I always disagree with you, it's just because I disagree, not because it's you.

Thank you.

hax commented 6 years ago

@mbrowne

Too late. If we were designing a new language I would agree with you, but JS has never had public properties and doesn't even use a public keyword for its public properties, so making public the default is much more consistent with the mental model of the language.

You are right at all. But you forget the other essential part of JS: var is function scope and class is just a better form for old constructor function. And let/const is block scope, and current proposal private field semantic also based on block scope (this.#x only works in scope of the class body).

So like class is a better form for old constructor function, classes 1.1 code

class Counter {
  var n = 0
  inc() { n++ }
  value() { return n }
}

is just a better form of

function Counter() {
  var n = 0
  this.inc = function () { n++ }
  this.value = function () { return n }
}

With such mapping, JS programmers never need to use "private" vs "public" mental model (it cause knee-jerk reaction like why not use Java-like private?), but instead use "class (local) scope variable" vs "object property (this.xxx)" mental model to get the same semantic of current private field! I hope you could agree "class scope variable" which use var or let/const keywords is consistent with the mental model of the language, not only syntax, but also semantic. On the contrary, this.#x only consistent with this._x in syntax, but never in semantic, which cause problems like this['#x'], dynamic enumeration, and it's even not easy to understand lexical rule of this.#x for average programmers (like "why super.#fieldOnBaseClass not work?").

Also, # is very short so you'd have to be really lazy not to use it even if you believe it's better.

I mean, compare to this.#x vs this.x, x as shorthand in classes 1.1 is much shorter than this.x, so the programmers will be happy to write a class start from all simple instance vars which is good for encapsulation.

rdking commented 6 years ago

@mbrowne

JS has never had public properties and doesn't even use a public keyword for its public properties, so making public the default is much more consistent with the mental model of the language.

SMH. How can you possibly use an argument like this and still support the current proposal? On many levels, it violates consistency with the existing mental model of the language. Here's a short list:

There's more, but these are the top 5, imo. Individually, they may seem excusable, but together they represent an utter rewrite of a sizable portion of the mental model. These days, a quarter is pretty useless and ignorable by itself. Get 5 of them together and you can actually buy something. That's what's happened in this proposal. Small, seemingly insignificant changes have been made with the thought that was is being prevented is worth the cost. However, do that too many times and you end up giving away far more than you may have anticipated.

@ljharb

i agree, it has that characteristic. However, being unable to declare necessary public properties is one reason that proposal is a nonstarter for me.

Ok. So what if 1.1 supported public properties (that's properties, not fields)? Would it be worth a second look in your opinion?

When exactly is declaring public data properties necessary? The only time I know that it is necessary is when there is no built-in concept of private in a class that fits the need. So can you give a real use-case for when public data properties are necessary?

Note: You need to distinguish data properties from accessor properties since public accessor properties can already be declared. Also, please don't mention code-bases with a design rule stating no accessor properties. That design rule exists in a language with no built-in concept of private in a class. The reason for that rule is to ensure that side-effects are only ever the result of a clear function call. This rule is subject to change as libraries begin to make use of encapsulated privacy.

rdking commented 6 years ago

@mbrowne I'm sorry, but there are so many things wrong with this paragraph!

These are good rules and this proposal does nothing that prevents you from following them. I get your point and I wish that JS had a policy of "private by default" from the beginning, but given that the current state of affairs is "public by default", I disagree that the language should force developers into this.

The idea wasn't to switch all objects to "private by default". That would make no sense at all (and break a lot of code). The idea is to take class, which currently has no data property support at all, and make it have data properties that are "private by default". This would be no different than what currently happens with closures, one of the two things currently used to implement private. This is not forcing anything on developers. They would still be free to continue using class exactly as they do now, without property definitions. I'm not saying I advocate that position. I'm saying your argument makes an assumption not claimed.

Given the current proposal, if you start out with a public property because it's a mutable property that you want to be accessible from the outside, and then need to change the implementation later, you can refactor without breaking any external code. That's a great thing, and something that's not possible to do in Java or C#.

That's also complete bologna. Anything that tries to wrap a non-membrane Proxy around that new variation instance will suddenly throw where it would work before. You cannot dismiss Proxy as an important tool in modern ES.

The inability to refactor in that way is really the only reason why otherwise "public" properties in Java/C# should be implemented using getters and setters from the start.

That has very little to do with the recommendation for getters and setters in Java & C#. The real reason is because it's very easy to send garbage to a public data property and really mess things up. Getters and setters allow you to validate that what you're getting/giving makes sense. It's basically blowback from problems like code/sql injection.

mbrowne commented 6 years ago

How can you possibly use an argument like this and still support the current proposal? On many levels, it violates consistency with the existing mental model of the language.

There are also good things about the proposed syntax that do fit the mental model, and as to the #, it was mostly a process of elimination that led to that choice. I think the most problematic one in your list is use of = without [[Set]] semantics, which is why I'm advocating for [[Set]]. There are plenty of other arguments in favor of the proposed syntax that committee members have made repeatedly.

The classes 1.1 syntax could work also, and I think that proposal would be more viable (practically speaking) if it included public properties. But I would still vote for this proposal because of this:

class Demo {
    var x

    foo() {
        // what the !#*???
        this->x
    }
}

By contrast, you can think of # as a required prefix for private properties, and once you are aware of that it's very similar to the existing syntax for declaring and accessing properties. Yes, developers could get used to either syntax but overall I think the proposed syntax is more intuitive. And I don't see how you could add public properties to classes 1.1 without making things more inconsistent and confusing compared with the current proposal.

They would still be free to continue using class exactly as they do now, without property definitions.

This is similar to saying that people are free not to use classes at all. Given how controversial classes were, I thought there would be more developers who would avoid them. But the benefits of a single standard way of doing something are just too compelling: even most of those who had gripes about them decided to use them, especially once everyone else did. In a few years it will be pretty unusual to see classes that still declare properties in the constructor (for new code).

Anything that tries to wrap a non-membrane Proxy around that new variation instance will suddenly throw where it would work before.

I'm not sure what you're referring to. The get and set in proxy handlers works the same way for public properties as they do for getters/setters.

That has very little to do with the recommendation for getters and setters in Java & C#. The real reason is because it's very easy to send garbage to a public data property and really mess things up. Getters and setters allow you to validate that what you're getting/giving makes sense.

I'm well aware of the reasons for using getters/setters and agree with them. But if you don't have such a validation need and don't even anticipate it, then in JS you can just make the property public. If such a need arises in the future, then you can refactor to a getter/setter.

ljharb commented 6 years ago

React requires a public “state” data property, for one, but anything that needs to describe an instance so that external code can read it (including superclasses or subclasses).

You say “properties but not fields”, but th thing that makes it a field imo is the initialization code that runs per-instance - that’s the thing that’s needed, so I’m not sure what it would look like to “not be a field”.

The “design rule” for “no getters/setters” will persist with private state available; a feature that does not allow for data properties isn’t meeting the existing needs - it being a design rule doesn’t change that.

hax commented 6 years ago

@mbrowne

But I would still vote for this proposal because of this

In 90% cases you only need shorthand syntax which is just x which is intuitive and the most ergonomics. It's a unfortunate classes 1.1 do not include shorthand syntax in the begin, but it just because it's only a pre-stage 0 proposal, and do not have many time to investigate all possible details.

hax commented 6 years ago

@ljharb

It seems you would like to use state in React as field? So you will write code like this:

class MyComponent extends React.Component {
  state = {...}
  ...
}

while React official documentation does not.

The “design rule” for “no getters/setters” will persist with private state available; a feature that does not allow for data properties isn’t meeting the existing needs - it being a design rule doesn’t change that.

You still don't provide the rationale of such "design rule".

ljharb commented 6 years ago

React’s official docs don’t use proposed features, generally; once public fields are stage 4 I’m sure it will. static propTypes = is also very common already.

If you want to discuss a rule in a specific style guide, please open an issue there. I brought it up not to debate the specifics, but as an example of a well-established idiomatic preference that exists in the community - and even though some of you may not feel like it based on this proposal, community preference and established convention is always considered.

hax commented 6 years ago

@mbrowne

By contrast, you can think of # as a required prefix for private properties, and once you are aware of that it's very similar to the existing syntax for declaring and accessing properties. Yes, developers could get used to either syntax but overall I think the proposed syntax is more intuitive.

I already analysis it, see https://github.com/tc39/proposal-class-fields/issues/144#issuecomment-431620174 , we can continue discuss it in that thread.

hax commented 6 years ago

@mbrowne

And I don't see how you could add public properties to classes 1.1 without making things more inconsistent and confusing compared with the current proposal.

I hope you can think it in another way!

This proposal has been developed several years and still many issues in almost every place. So there may be something essentially wrong in the design.

On the other side, classes 1.1 only pre-stage 0 proposal, and the only big concern is just missing public field. I think if we can put more resource on it, it's very possible to get a better result than this proposal.

hax commented 6 years ago

@ljharb

React’s official docs don’t use proposed features, generally; once public fields are stage 4 I’m sure it will. static propTypes = is also very common already.

Actually React docs use public field for arrow functions, but still write this.state = ... in constructor. I think React should never adopt class ... { state = ... }, unless the proposal use [[Set]] instead of [[Define]]. Of coz, our ideas is just our's, we should ask React guys how they think.

If you want to discuss a rule in a specific style guide, please open an issue there. I brought it up not to debate the specifics, but as an example of a well-established idiomatic preference that exists in the community - and even though some of you may not feel like it based on this proposal, community preference and established convention is always considered.

Fine, if you refuse to discuss your rationale but declare your rules are well-established arbitrarily , let's just stop here.

ljharb commented 6 years ago

What i mean by “well-established” is that they are widely accepted and used. Whether they are well motivated is a separate and leas relevant discussion. I’m more than happy to discuss my rationale in the appropriate venue, but this repo isn’t it.

As for React, there’s no setter for state at the moment - react is far too performance-sensitive to ever use getters/setters, i suspect.

mbrowne commented 6 years ago

For anyone following this discussion... Re: React, see https://github.com/tc39/proposal-class-fields/issues/151#issuecomment-431679834

hax commented 6 years ago

@ljharb

What i mean by “well-established” is that they are widely accepted and used. Whether they are well motivated is a separate and leas relevant discussion. I’m more than happy to discuss my rationale in the appropriate venue, but this repo isn’t it.

Your coding style as whole is widely accepted and used doesn't necessarily mean every rule of it are all widely accepted and used. For example, some who prefer tabs or 4 spaces could easily override your two spaces rule (thank great ESLint for the flexibility!)

Of coz, tab/space is a bad example, but your do not use setter/getter rule even never have a eslint rule, so basically it's never have evidence your such rule is widely accepted. I'd like to say most of users of your eslint-config may never know you have such a rule! For example, I myself never notice you have such rule, though I have investigated many js coding style include yours. As usual, you can ignore my explanation, but I'd suggest you write a eslint rule which really forbid the usage of getter/setter then you can get the real feedback.

As for React, there’s no setter for state at the moment - react is far too performance-sensitive to ever use getters/setters, i suspect.

I already pointed out, as the jsperf test , there is no significant performance difference between property and simple getter/setter in recent engines. Actually getter is a little bit faster in Firefox for some unknown reasons...

mbrowne commented 6 years ago

@ljharb

You say “properties but not fields”, but the thing that makes it a field imo is the initialization code that runs per-instance - that’s the thing that’s needed, so I’m not sure what it would look like to “not be a field”.

Ok now I'm confused. I thought that the difference between fields and properties was:

Is my understanding completely wrong or different from how others are understanding this?

ljharb commented 6 years ago

That sounds right to me, but with the addition that fields have an initializer - code that runs per instance to create it. Properties don’t have that. Set vs Define is a difference that could apply to either one.

rbuckton commented 6 years ago

I'm not particularly fond of fields using [[DefineOwnProperty]] over [[Set]] as there are some possible sources of confusion for users. Consider this:

class Base {
  x = 1;
}

class Sub extends Base {
  get x() { return this._x; }
  set x(value) { this._x = value; }
}

A developer might expect the getter/setter on Sub to trigger, since x is declared on Base. In TypeScript, this works because we use [[Set]]. However, in the current proposal if the field defines an own property during initialization, the getter/setter of the subclass won't trigger. This completely prevents me from being able to override superclass functionality from within a subclass.

I personally prefer the [[Set]] only approach as it is the minimum capability needed for this proposal, and is the most flexible. If the shadowing behavior of [[DefineOwnProperty]] is desireable for a specific application of the feature, it is feasible to define a decorator that adds an undefined-valued property to the prototype to shadow a same-named property on the supertype.

However, If move forward with [[DefineOwnProperty]] we cannot easily use a decorator to achieve the [[Set]] only behavior (at least, not without possibly significant constructor wrapping).

nicolo-ribaudo commented 6 years ago

It would be too hard to create a decorator to use [[Set]] instead of [[Defined property]], see https://github.com/tc39/proposal-class-fields/issues/151#issuecomment-431185428

rdking commented 6 years ago

@nicolo-ribaudo actually, it's just short of impossible without either putting properties on the prototype so you have something to modify, or having the presence of a decorator automatically switch the field definition into [[Define]] mode. It's unfortunate, but it's a direct side-effect of the way the current proposal is handling things.

@mbrowne Wow I'm late with this reply.... I get that there are "good things" about the current proposal. So let me put it on record: I want to keep as many of the "good things" about the current proposal as possible! However, I am by no means willing to sacrifice any organic expectation about functionality or syntax for the sake of any of those "good things." This proposal sacrifices too much, and while lauding that private fields will not affect external code, I can already tell you definitively that a fairly sizable portion of the community will be negatively impacted the first time one of the major libraries implement using this proposal.

As for the 1.1 proposal, starting with this thread, I've been working with that to fill in some of the missing parts based on the conversations in these threads. Wouldn't mind getting more input.

littledan commented 6 years ago

@rbuckton Would https://github.com/tc39/proposal-decorators/issues/44 be sufficient to create a decorator to make a [[Set]]-style field?

rbuckton commented 6 years ago

@littledan: While that would make it technically feasible, I am not particularly in favor of the approach outlined in the issue. I strongly believe [[Set]] should be the default semantics for fields. [[DefineOwnProperty]] seems like a tremendous footgun that won't work the way that developers expect.

mbrowne commented 6 years ago

@littledan I propose the opposite: there are very good reasons to make [[Set]] the default and to say that anyone who wants [[Define]] semantics can use a decorator to achieve that. I don't think the reverse of this argument is nearly as convincing. What major issues and footguns are prevented by making [[Define]] the default?

rdking commented 6 years ago

@mbrowne It doesn't solve any foot-guns, only introduces a big one. What it does, however, is enable decorators to work. The problem, from the point of view of this proposal, is that a decorator requires a property descriptor to operate on. [[Define]] semantics means that descriptor gets created. [[Set]] semantics only generates a descriptor if the prototype doesn't contain the desired property, or the prototype's property is a data field. That creates an iffy situation for decorators when the field is inherited.

curiousdannii commented 6 years ago

Does the Babel plugin perfectly mimic the spec's [[DefineOwnProperty]] semantics, does it have the proxy complications that have been raised?

mbrowne commented 6 years ago

@rdking Do you know if anyone has already given a counter-proposal for decorators that would work together with [[Set]] semantics as the default for class properties?

littledan commented 6 years ago

@curiousdannii In spec mode, I believe it does. In loose mode, it uses [[Set]].

@mbrowne Not that I know of.

rdking commented 6 years ago

@mbrowne I'm fairly certain that no direct counter-proposal can be formulated. The truth is that decorators is 100% reliant on the existence of a property descriptor. There's a simple solution to the problem, though. I've been pushing for this since I discovered the problem. Make everything within a class definition a property of the products of that definition. That is to say, whether it be a function, accessor, or data property, it belongs on either the prototype or the constructor. If that's done, then [[Define]] semantics will be consistent with what already exists, and decorators will continue to work as they're designed.

It's the attempt to keep data properties off of the prototype just to avoid the accidentally-shared-object in the prototype foot-gun that's causing the issue. The foot-gun can be just as easily avoided by putting the field on one of the class products and either

  1. using [[Set]] semantics in the constructor to give it a value, or
  2. restricting what can be assigned in the definition to only include primitive values available at the time of definition and frozen objects.

Case 1 is virtually identical to what is being done now. It has only 1 drawback. If a class is designed to be used in an abstract way such that a derived class completes its functionality by providing needed missing data and functions, this approach will fail to provide data to the constructor. This is a potential problem, but also a design pattern probably not all that familiar to ES developers, so the value of this issue is debatable. There's also the issue of implicit override, but that can be remedied by restricting override so that a downgrade from an accessor to a data property is not allowed in a class definition.

Case 2 skips the need for constructor manipulation at all by allowing data to be assigned directly to the prototype and constructor properties. The limitation is that Objects of any kind that are not frozen cannot be set. There's a way around this. The problem comes from the fact that Objects are references, and Object duplication does not occur if any portion of the object is edited. This can be remedied with a Membrane that, on the first attempt to modify the object in any way, it is first duplicated (via a function created in the proxy handler at definition time), applied as an own property of the instance, and the operation carried out on that duplicate. If this kind of Membrane is implicitly used during on every assignment to a data object in the class definition, then the foot-gun will have been completely remedied with no need for any restriction.

I never said it was an easy problem to solve, just that the solution exists.

mbrowne commented 6 years ago

@rdking As you know, prototype properties still have property descriptors. So it seems the challenge for decorators isn't just a reliance on a property descriptor, but the reliance on an own property descriptor. I'm not sure the two options you've presented are the only ways of solving this, but I need to think about it some more.

rdking commented 6 years ago

@mbrowne Seems like you may have misunderstood something. The point to preserve the functionality of descriptors is to put the data properties on either the prototype or the constructor function (class products) as needed since that will give the needed property descriptors. Not doing so is what risks the existence of a "field" that cannot have a decorator if the use of [[Set]] semantics in the constructor is followed "field" declarations.

I didn't bother dealing with the "own property" distinction since it doesn't actually matter. If [[Define]] semantics are used in the constructor (as currently prescribed), "fields" become an "own property" of the instance. This seems to be what TC39 wants to do, despite the issues it will cause. If, however, class is restricted to only operating on it's own products (which is consistent with how they currently work) then data properties would simply be "own properties" of the constructor and prototype objects, and the argument about [[Set]] vs [[Define]] semantics goes away completely. At no point are we ever discussing anything that will not be an "own property".

mbrowne commented 6 years ago

@rdking I think I was using the term "own property" in a different context than you were. I was just referring to instance vs. prototype, and probably should have said "reliance on a property descriptor created on each instance". (Yes, a prototype is an object too, so it has its own "own properties", but I was speaking from the perspective of the instance.)

rdking commented 6 years ago

@mbrowne I get it. Does the decorator proposal really depend on instance properties? When I read through it, there was nothing in it that seemed to require the property to be on an instance. If that were a requirement, then what of decorators applied to public functions? The public functions are unarguably on the prototype. So such a decorator would either have to modify the prototype property, or unnecessarily duplicate the function property onto the instance just to get the desired descriptor settings. That would be problematic for code that works by mutating prototypes.

trusktr commented 5 years ago

+1 for a step back.

Can class fields/properties be a separate proposal from access modifiers?

Once we have class fields settled (I prefer it to use [[Set]]), then we can move onto access modifiers. In my opinion they are independent of each other: class props merely define a short hand for creating properties on instances, which honestly has nothing to do with access modifiers.

mbrowne commented 5 years ago

@trusktr The community has been pressing the committee to add private members for many years, which was one of the major reasons that the previously separate proposals (public fields and private fields) were combined into this proposal in the first place. As @littledan put it at https://github.com/tc39/proposal-class-fields/issues/100#issuecomment-428968302:

In addition to not having complete community consensus on all aspects of this proposal, we also don't have community consensus to delay private fields further. That's why it comes down to a hard decision. If everyone outside of TC39 were happy with this taking a few years longer, then I'd like to think that we'd be delaying it further.

Regarding your syntax proposals from the other thread, please see the private syntax FAQ. You might still disagree as others have done, but at least we won't have to revisit the same territory for the 1000th+ time.

mbrowne commented 5 years ago

P.S. See also https://github.com/tc39/proposal-class-fields/issues/136#issuecomment-430660769 and previous comments on that thread.

rbuckton commented 5 years ago

@rdking:

The truth is that decorators is 100% reliant on the existence of a property descriptor.

That's not correct. Decorators are 100% reliant on the existence of a property declaration and a member descriptor. If we used the [[Set]] semantics for public fields, then the Decorators spec could be modified to not require the descriptor property at all for a public field (i.e. { kind: "field", placement: "own", key: "foo", initializer: () => bar }).

hax commented 5 years ago

@rbuckton It's alway possible to make decorator spec workable. But it definitely make the decorator author's life harder, more complex semantic ask more complex code, and ask more bugs, and ask more pain for both authors/users of decorators.

Note, I believe [[Define]] on own property is insane because it just ruin ES6 classes inheritance. But [[Set]] semantic on own property is also bad because of the complexity. The only simple and good semantic is [[Define]] on prototype or private (private never inherit). Actually, consider private slot is also a special thing for decorator and add complexity, the most reasonable solution is using private symbol solution, which is just plain property! Then everything is fine.

rdking commented 5 years ago

@rbuckton You kind of made my point for me. What good are decorations that decorate nothing? Decorators are meant to create/modify/delete properties of a class (or instance, apparently). To modify or delete such, it needs to know the definition of that property, i.e. it's property descriptor. This makes [[Set]] semantics a complex issue for decorators, and is probably one of the primary justifications for fields using [[Define]] semantics and breaking class inheritance.

Inheritance itself also presents a complex case for decorators on fields since the base class could be altered after the fact to make a field non-configurable on the base, essentially breaking any decorator for the same name on the derived. In the end, the more I look, the more I see an ever increasing, devolving chain of complexity just to avoid a single foot-gun that's easier to stop by other means.

mbrowne commented 5 years ago

@rdking You seem to have forgotten this statement by @ljharb (https://github.com/tc39/proposal-class-fields/issues/151#issuecomment-437527235):

Decorators will be roughly equivalently difficult to implement either one with either default, imo.

I have seen at least one comment from another committee member who thought decorators would be more problematic in conjunction with [[Set]], but it would be an assumption to say this was one of the "primary" justifications for choosing [[Define]].

I agree with @rbuckton and @ljharb: decorators with [[Set]] should be totally doable. If you want to get into the details we could discuss it in the decorators repo. The bigger issue is that the rest of the committee seems to prefer [[Define]] even without considering decorators, and doesn't seem interested in fully explaining their position or considering anything else that people have to say on the matter.

It seems like most of the committee has largely tuned out of the discussions here, which I think is unfortunate but I also understand it—as @littledan put it, they're mainly hearing from the same few of us again and again "at great length". I'm happy to continue participating in discussions, but I don't have much hope at this point that it will make much difference. Personally I would be OK with the current class fields proposal shipping as-is...while I strongly disagree with the choice of Define, it's not a deal-breaker for me (and I can understand why @ljharb finally acceded to it to achieve consensus). But regardless of my own position, if we want our most recent feedback to have even a small chance of really being considered, we might want to consider a different format for it. Your class-members proposal is one such alternative format. Some sort of wiki consolidating feedback in a concise format might be another.

rdking commented 5 years ago

I didn't forge that. I simply recognize that a decorator without a field definition to modify is more of a class finalizer than a field decorator. When thought of in that way, he's right. I'm merely pointing out that without a field definition, it's not a field decorator anymore.

I also get what @littledan is saying. After a while, it starts to sound like dogs barking in the background. Keeping with that analogy, I'm one to think "if the dogs are still barking, maybe I missed something important." I wish they'd also take that perspective.

trusktr commented 5 years ago

Regarding your syntax proposals from the other thread, please see the private syntax FAQ.

@mbrowne I read those already. I disagree with technical parts. private foo = 'foo' with private.foo, static foo = 'foo' with static.foo, and static private foo = 'foo' with private(static).foo are all technically possible in place of #. #90 is nice from a syntax perspective: easy to read, aesthetically pleasing, semantic.

The only problem the FAQ has against private/protected keywords is described for example under "Why aren't declarations private x?". I disagree with those too. What the FAQ says there is like saying developers learning JavaScript are incapable of reading documentation.

Just like var declarations don't work the same as in other languages (because hoisting), protected and private don't have to work the same as other languages.

As mentioned in https://github.com/tc39/proposal-class-fields/issues/177, the expectations that people will have about protected and private keywords (when coming from other languages) will pale in comparison to learning var and callback hell in JavaScript.

Honestly saying, we should make protected and private in JavaScript be the most awesome of any language, and document it well.

JavaScript's private and protected should make the other languages jealous!


So I think it's worth revisiting that territory because that territory is currently an opinion, and in my opinion diverging from "protected" and "private" keywords because they exist in other languages and ours would be slightly different is just not a strong argument.

If one doesn't like to read docs (and likes to make lots of possibly incorrect assumptions), then that person will get bitten in many other languages, so that argument is simply just not a good reason why JavaScript shouldn't have the best version of protected/private that the world has seen (one with prototypical inheritance!).

trusktr commented 5 years ago

community has been pressing the committee to add private members for many years

Looks like we're almost there now that classes and static are permanently here. A syntax change won't delay it much, especially with all these Babel/Buble/TypeScript compilers existing after the advent of ES6.

rdking commented 5 years ago

@trusktr There are so many different camps in this private data discussion that it's somewhat daunting. For whatever reason(s), TC39 is somewhat passively forwarding class-fields, knowing full well the issues it will cause the community but deeming those issues, whether in part or whole, not enough to warrant reformulating the proposal. Even though there are several members of TC39 who do not support this proposal, they apparently don't feel it worth while to veto this proposal.

Sadly, proposals like what's in #90 have been offered and shot down for being too verbose. Apparently brevity of syntax is weighted more important than clarity and expressiveness. That, and there's a somewhat strong desire for the syntax to resemble what exists in other languages, despite the fact that the opportunity for that ship sailed when they based class on the pseudo-classical approach that was available in ES3.

The long and short is that, where private data is concerned, unless a large enough population within the community is made aware of the problems with this proposal and makes TC39 aware that this is unacceptable, this proposal is going stage 4 as soon as an engine other than V8 has it implemented. It's not like there aren't better alternatives, though.