Closed littledan closed 6 years ago
I have found it helpful to view the subclassing hazard with respect to private static fields as an instance of a more general observation:
Since private state access does not traverse the prototype chain, private state is not appropriate for objects that are intended to be used as prototypes.
Consequently, we should not add syntactic support for private state to forms that produce objects that are frequently used as prototypes. Those forms include:
On the other hand, the lack of static private fields will be difficult to explain if we have static public fields, instance public fields, and instance private fields. In this case the "hole in the grid" is far more conspicuous than in the case of, for example, "async getters" (which don't make a whole lot of intuitive sense to begin with).
It appears that we're stuck in a classic dilemma - we should take care to make sure that it is not a false dilemma.
I'm surprised to see object literals included here. Object literals can be used as prototypes, but it seems like a stretch to say they are frequently used to the point where private fields in objects become inappropriate altogether.
I'm aware that the case for including object literals in that list is weaker and potentially controversial, and I don't want to sidetrack the discussion over it.
To briefly elaborate though, suppose we allowed the following syntax:
const obj = { #x: 1, get x() { return this.#x } };
Are we giving users enough context to tell when such private field usage is appropriate versus inappropriate? It looks just like a regular object with a cool new feature. Given the fact that we can already use closed-over variables to implement per-object-literal hidden state, it may be better not to tempt users into making a wrong choice.
In the case of class instances, the class syntax (along with the behavior of the builtins) provides the necessary context.
My concerns are:
I've come to the position that the "hazard" is not surprising if one internalizes the mental models of both this
being a dynamic receiver and private names as lexically scoped property names that are usable on objects of certain provenance. That is, private names combine both lexical scoping and property access, with a crucial restriction on the receiver. Private names seem useful to me, especially with use cases listed in @littledan's post.
So indeed, private names don't participate in prototype inheritance. I'd like to understand better the intuition that private names should participate in prototype inheritance come from, that seems to be against their point as, well, private.
I’m in the same camp as Kent: I find public static fields very useful, but would postpone private static fields (since module-private data provides an easy work-around).
@rauschma Are you talking about @zenparsing (Kevin), or who do you agree with?
@littledan I’m referring to the tweet by Kent C. Dodds that you quoted in your slides: https://twitter.com/kentcdodds/status/942264424265482240
@rauschma Oh I see. What do you think of "continuity" as a goal here (which I think is similar to what @waldemarhorwat is getting at)?
@littledan I’m not sure.
@syg and I discussed this further, and we'd like to put static private fields and methods back in this proposal. As Shu explained, the semantics of private static fields and methods is analogous to the public forms. We'll work on updating this repository for this change and improving educational materials.
In the March 2018 TC39 meeting, I argued for decoupling static public fields from static private fields and methods, but we heard some skepticism from some committee members about this plan. Some sources of this skepticism:
Can we eliminate the private static subclassing hazard?
If we add private static fields and methods, I think the answer is no. We've investigated several alternatives in this repository, and none seem feasible.
In individual discussions with people who raised the subclassing hazard, who are also some of the strongest proponents of static private fields and methods, I heard a common response that the original, "hazard" semantics are acceptable if this is the only feasible alternative, especially in conjunction with a shorthand syntax for accessing properties of the constructor.
What about the other follow-on proposals?
Many committee members are strongly in favor of additional follow-on proposals documented in this repository, including static blocks in classes and lexically scoped private names outside of classes. Static private fields and methods serve some, but not all, of the same use cases as these other features.
If we advance static private fields and methods, we may want to think through the following propositions about how this would affect how those other features are taken:
If it would be hard for us to make either one of those statements, this is an argument in favor of proceeding with caution on private static, which might lead us to maintain the current split and advance static public fields alone.
Due to the partially overlapping features, the recently raised "hazard", and the lack of experience with these features with these particular semantics in transpilers, it may be worth it to study them longer before proposing for Stage 3, in a way analogous to the approach the pipeline operator proposal is taking.
How should we decide from here?
The contents above are based on conversations between a few TC39 members. I'm interested in expanding the discussion. What do you think of all of this, and how TC39 should proceed?
Note, let's leave out of this thread discussion about the existence of instance private fields and methods, or the
#
syntax, in this particular thread. There are several other threads in the class fields and private methods repositories which discuss these topics. Unfortunately, I may need to moderate this thread to keep it on topic.