Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Revival of the "protected" request #122

Closed
rdking opened this issue Aug 13, 2018 · 91 comments
Closed

Revival of the "protected" request #122

rdking opened this issue Aug 13, 2018 · 91 comments

Comments

@rdking
Copy link

rdking commented Aug 13, 2018

I've had prior discussions with @ljharb over this issue. The gist of his argument is that protected provides no actual protection from access in any way that significantly matches the capabilities of the same keyword in other languages. As a soft-private feature, it doesn't fit the goals for the proposal.

This is where we disagree, tremendously, and for 2 very good reasons.

  1. protected can have the same effect in ES as it does in other languages supporting the concept.
  2. protected never provided anything like security in other supporting languages.

The argument against having protected has often been like this:

  • With ES, any class can be subclassed at any time.
  • Any class instance will have access to the protected members of any other instance.
  • Therefore protected access is still public.

This argument is very much correct. This cannot be argued against. However, it's just a strawman argument. It misses the point entirely. This kind of argument presumes that the point of protected is to actually "protect" something. This is not true. In every language that uses it, protected is a tool for separating the API space of an object into 2 separate domains: 1 for members, and 1 for non-members. Even in a statically compiled language, a developer can create a class with methods that leak the protected members of its base. ES, being a scripted language, just makes this leak ability a little less time consuming.

This fact is very much irrelevant to the purpose of protected. Where private has always meant that the declared item is an internal implementation detail that cannot be seen, protected has always mean that the declared item is an internal implementation detail that is shared with extending objects but not with unrelated objects. The only cost of entry is the ability to extend the original.

Hopefully, that clears things up around what protected is supposed to mean. Now, why do we need it? Presently, everything is either public, or through great pains (WeakMap & closures) hidden. Even before the debut of the class keyword as a functional part of the language, developers were creating object factories that mimicked the behavior of classical OOP hierarchies.

Unfortunately, there was no simple approach to providing any semblance of protected support. So what developers began to do is adopt the '' convention where any member whose name begins with an '' is treated as an implementation detail. The developers would, of course, use these so-called implementation details in their own subclasses, but had no way of guaranteeing that other developers would play by the same rules. The protected capability is what can provide that functionality. That is indeed the original intent.

If a protected member is treated as a "public member of the private interface", then any object or method with access to the private interface will have access to the protected members. The benefit of doing this is an increase in the clarity of the design of all object hierarchies using this feature. Library developers will be able to create clear and clean external and internal APIs while protecting all of the implementation details inside the private interface. This will also increase the readability of any code using such libraries as it will be clear from the deliberate use of inheritance whether or not various members were intended to be publicly exposed. This will in general lead to an overall improvement in the quality of all adopting code.

I am not proposing here that protected support be added to this proposal. What I am asking for instead is that this proposal be reworked such that protected support becomes a natural extension to be added at a future time. As it stands, lacking protected support means that developers will still continue to use the _ convention as there will still be no means of hiding the shared internal API of their libraries from the publicly exported interface.

@piranna
Copy link

piranna commented Aug 23, 2018

I have read recently about a final class keyword, preventing the class to be inherited, so protected attributes would not be able to be accessed from a child class because it would not possible to have child classes. Alternatively, they would be added final protected attribute, being private for child classes but being accesible by friend ones.

@rdking
Copy link
Author

rdking commented Aug 24, 2018

final class would simply mean that protected would be a SyntaxError in such a class. Those two keywords are mutually exclusive. If they're going to implement final class, then they should also implement abstract class which would prevent instantiation without inheritance. Something like final protected should probably work like const protected but only for functions, marking the function as non-configurable, non-writable.

@mbrowne
Copy link

mbrowne commented Sep 14, 2018

I agree that some form of protected could be good to have, for the reasons you cite, especially in the absence of friend and other access modifiers (which I think would be good to have too). This could be accomplished with decorators (either in userland or by introducing some sort of native decorator), but ultimately I think it makes more sense for it to be a core feature of the language -- same with soft private and friend. At this point I don't think it's realistic for any of these additional access modifiers to be included in the current proposal, and I agree with @littledan's earlier point about letting people experiment with creating their own access conventions using decorators as a laboratory to discover what works best, before formalizing final decisions in the language.

But I think the syntax and semantics of protected and other access modifiers are still worth discussing now - it's always good to keep an eye toward the future.

@littledan
Copy link
Member

@mbrowne sums it up well: this is a promising area for investigation, but it's most realistic to consider it as a follow-on proposal. Would someone be interested in creating another repository to discuss the idea? In this repository, I would be most interested in discussing any reasons why this more minimal proposal might make it difficult or impossible to add protected as a future proposal.

@mbrowne
Copy link

mbrowne commented Sep 15, 2018

I was initially concerned about the lack of reflection capabilities in the current proposal, and I still have some concerns about calling hard-private fields just "private", since they work differently than "private" in other languages and will probably be overused by some developers, at least at first. (Although IMO that would often be better than underusing them and having no encapsulation...and the terse syntax # is actually an advantage from this perspective.)

But I do think we have a good path forward to address these concerns. For example we could have:

class Demo {
  @reflect
  #softPrivate
  
  protected foo
  friend bar
}

And maybe also the ability to enable reflection on all fields in a class with a single decorator:

@reflectAllProperties
class Demo {
  #foo
  #bar
}

@Igmat
Copy link

Igmat commented Sep 25, 2018

@littledan it seems that there is a concern about syntax of private in this proposal. While it's really nice syntax when there are only 2 options - private and public:

class MyClass {
    foo;
    bar;
    #someImplementationDetails;
}

It doesn't seem to be a good choice if there will be more access modifiers, like protected:

class MyClass {
    foo;
    bar;
    #someImplementationDetails;
    protected someProtectedFiled;
}

It seems weird that access modifier in one case is word and in other case is just leading sign. Obviously, we could try to use leading signs for all access modifiers, like this:

class MyClass {
    foo;
    bar;
    #someImplementationDetails;
    ^someProtectedField;
}

But this is counterintuitive and complex. Newcomers will have to also learn all kinds of signs that is used to set access level for field.

In my opinion it's better to adopt well-known syntax from other languages:

class MyClass {
    foo;
    bar;
    private someImplementationDetails;
    protected someProtectedField;
}

Also this kind of syntax is already adopted by huge part of ES community that use TypeScript (see docs).

So this approach could be applied with only private keyword, while other types of access modifiers could be a follow-up and independent proposals.

Additional note about motivation and protected

IMO it's very important feature, especially for library/framework authors, as were already said - it's not about securing some values, but for providing another kind of public interface.

I'll try to explain it using example. Let's imagine that I've created a library (named CoolLibrary) with such class:

class MainClass {
    partialMethodA(...args) {
        // pure function
        // does some work, but it's useless by itself
    }
    partialMethodB(...args) {
        // pure function
        // does some work, but it's useless by itself
    }
    partialMethodC(...args) {
        // pure function
        // does some work, but it's useless by itself
    }
    mainMethod() {
        // actually does some very useful stuff, by extensive use of:
        // partialMethodA, partialMethodB, partialMethodC
        // and this method is a reason why end-users will need it
        // code designed for working in some specific circumstances
        // and, unfortunately, couldn't be very generic
    };
}

mainMethod is obviously public, question is about partialMethodA, partialMethodB and partialMethodC. They are building blocks for mainMethod and could be hidden under private for majority of the users.
But library also provides ClassA, ClassB, helperFnA, helperFnB, etc. And all of this additional stuff relies only on public interface of MainClass (which is mainMethod).

All this tooling solves some problem (lets mark it ProblemA) and does it well, but there are also ProblemB, ProblemC, ProblemD - all of them are pretty close to ProblemA, but have important differences.
Luckily, we could use CoolLibrary provided with another implementation of MainClass.mainMethod to solve this problems.

So somebody else created MainClassB for solving ProblemB, MainClassC for ProblemC and MainClassD for ProblemD. There also could be problems E, F, G and others. All of them are specific and couldn't be covered by one solution, so there will be developers who creates this MainClass{problem} implementations - and we want to make their life easier.
Implementing mainMethod without access to partialMethodA, partialMethodB and partialMethodC is very hard, so we can't mark those methods as private, but making them public is wrong, because end-users don't need them, since they have mainMethod. One of the good solutions here is separating interface for extensions authors and for end-users by applying protected to partial methods.

@mbrowne
Copy link

mbrowne commented Sep 25, 2018

@Igmat Have you seen the private syntax FAQ?

@Igmat
Copy link

Igmat commented Sep 25, 2018

@mbrowne and I don't see any problems with declaring as private x and accessing with this.#x

@ljharb
Copy link
Member

ljharb commented Sep 25, 2018

@Igmat unfortunately, if one person doesn't see problems, and another does, then there's still problems with it overall. Many of us find that asymmetry very problematic.

@mbrowne
Copy link

mbrowne commented Sep 25, 2018

Indeed, given that this.#x and this.x are not the same thing, I think many people would find private x very confusing.

@Igmat
Copy link

Igmat commented Sep 25, 2018

@ljharb but problem still presents. #-syntax is unextendible - we won't be able to add any other access modifier without introducing inconsistency in property declarations.

@ljharb
Copy link
Member

ljharb commented Sep 25, 2018

@Igmat i think it's a very large presumption that we'd ever add another access modifier, since JS doesn't have "access levels" - it just has "visible/reachable" or "unobservable/unreachable".

@Igmat
Copy link

Igmat commented Sep 25, 2018

And what about motivation for having protected I described above? It's important to separate interfaces for different kind of users.

@ljharb
Copy link
Member

ljharb commented Sep 25, 2018

@Igmat anything that's reachable is public; anything that's not is private - there's only one kind of interface users see.

@Igmat
Copy link

Igmat commented Sep 25, 2018

@ljharb what are the boundaries of reachable?
In browser and node.js (which probably are almost all ES use cases) I can easily access any code as a string, change whatever I want (e.g. print private property) and run this code even using eval.
Does it mean that there are no unreachable code at all and everything should be just public without any access modifiers?

@Igmat
Copy link

Igmat commented Sep 25, 2018

@Igmat unfortunately, if one person doesn't see problems, and another does, then there's still problems with it overall. Many of us find that asymmetry very problematic.

It was your words @ljharb. And I'm not the only one who see problems with # syntax and its possible extensions (e.g. protected). Why do you just keep posting that nobody needs other access levels?

@ljharb
Copy link
Member

ljharb commented Sep 25, 2018

"reachable" means "you can write javascript that can access it and/or observe its presence" (barring Function.prototype.toString, because otherwise the discussion is meaningless).

I'm not saying nobody "needs" or wants other access levels, I'm saying JavaScript inherently does not have any others, and so no such feature would likely make it through the standards process.

@littledan
Copy link
Member

@ljharb I'd prefer that you not take such a decisive tone in responding to these comments, e.g., in #122 (comment). One purpose of discussing TC39 issues on GitHub is to ensure that the community has a place to give feedback to the committee, let us understand what they're saying, and then be able to draw a conclusion with the benefit of those points of view.

@mbrowne
Copy link

mbrowne commented Sep 25, 2018

@ljharb I was just about to comment on this...

no such feature would likely make it through the standards process.

To echo what @littledan said, this seems like an overly strong statement. Obviously you have the first-hand experience of being on the committee and I don't, but your statement implies that it would likely never happen. There's a big difference between something not happening soon, and it never happening. I hope the committee is more open-minded than that, even if it ultimately decides not to add native support for any other access modifiers.

In addition, there are multiple ways that access levels in between hard private and public could be supported, both with and without native support. In other threads we have discussed how decorators could be used for this, e.g. @protected or @friend. If these decorators were to become very popular, is it inconceivable that somewhere down the line the language could offer native support for them for improved performance and more correct semantics? (Either as decorators with native implementations, or with protected and friend keywords, depending on which proposal made the stronger case.)

@mbrowne
Copy link

mbrowne commented Sep 25, 2018

@Igmat One other consideration is that private properties and methods can already be shared among the same module without being made public simply by not exporting them. That doesn't cover all the use cases for protected of course, but in some cases it may be preferable. It's not always good to couple inheritance with accessibility (as argued by Apple when explaining the absence of protected in Swift: https://developer.apple.com/swift/blog/?id=11).

@doodadjs
Copy link

Keep in mind that many people are resistant to bringing a class-system into Javascript, most of them preferring C-like API constructs. And also many absolutely don't like inheritance at all, which "protected" is for.

@Igmat
Copy link

Igmat commented Sep 25, 2018

Ok, what about next compile from ESwithProtected to ES5?

class A {
    protected foo = 'bar';
}
class B extends A {
    protected bar = 'baz';
    baz() {
        console.log(protected.foo); //prints bar
    }
}

Result:

const protectedSymbol = Symbol('not shared symbol, probably hidden in some scope');
var A = (function () {
    const protectedVars = new WeakMap();
    function A() {
        protectedVars.set(this, { foo: 'bar'});
    }
    A[protectedSymbol] = function (thisArg, property) {
        return protectedVars.get(thisArg)[property];
    }
    return A;
}());
var B = (function (_super) {
    __extends(B, _super);
    const protectedVars = new WeakMap();
    function B() {
        var _this = _super !== null && _super.apply(this, arguments) || this;
        protectedVars.set(_this, { bar: 'baz'});
        return _this;
    }
    B[protectedSymbol] = function (thisArg, property) {
        const ownProtected = protectedVars.get(thisArg);
        return ownProtected.hasOwnProperty(property)
            ? ownProtected[property]
            : _super[protectedSymbol] && _super[protectedSymbol](thisArg, property);
    }
    B.prototype.baz = function () {
        console.log(B[protectedSymbol](this, 'foo'));
    }
    return B;
}(A));

Obviously it's not a bullet proof and full example (e.g. prototype/instance methods should be covered in a little bit another way), but rather Proof-of-Concept, that something like protected could be achievable.

More of that - it doesn't use something very heavy - core principal is very close to prototype chain and I believe could be optimized by JS engines in same way.

Obviously, it's unlikely to find another addition to #-sign to cover protected case. And using # for private and protected for protected will blow our minds and even worse than dozens of different signs IMO.

Also, TS is huge part of ES community - and #-sign doesn't seem to be compatible with theirs access modifiers, but they declaring ES-compliance, so they will have to either throw away some parts of their tool (like, they already did with namespace), become incompatible (which very unlikely) or support two kinds of syntax for one feature, which will do mostly the same but still different things.

Why not to use already-existing ES keywords (private, protected) and avoid this problems?

@bakkot
Copy link
Contributor

bakkot commented Sep 25, 2018

Why not to use already-existing ES keywords

Please see the FAQ.

@Igmat
Copy link

Igmat commented Sep 25, 2018

@bakkot do you think I can't read? Or it's just auto-reply when somebody suggests to use existing keywords, because argumentation in FAQ has mostly nothing about why # declaration is better then keyword declaration, has nothing at ALL about access to private/protected via keyword instead of #, but contains a lot about implementation details and some engineering concerns.

I realize that this work is very hard - but I haven't seen ANY real problems for implementing it with keywords, only refering to FAQ which has not enough argumentation.

Will it help if I create babel plugin for private/protected using keywords?

@bakkot
Copy link
Contributor

bakkot commented Sep 25, 2018

@Igmat The section in the FAQ to which I linked is titled "Why aren't declarations private x?", and attempts to capture my answer to "why # declaration is better then keyword declaration". I don't know what else I can say on that topic.

has nothing at ALL about access to private/protected via keyword instead of #

That's true. That's been proposed a few times, but it seems like very few people want to use a keyword for access (especially if they couldn't have private x for declaration, which I think must continue to be the case for the reason given in the FAQ section I linked above), so I didn't think it worth covering in the FAQ. There's more extensive discussion in tc39/proposal-private-fields#14 if you like.

Will it help if I create babel plugin for private/protected using keywords?

No, probably not. I agree it's possible to implement with some semantics. That's not the issue.

@Igmat
Copy link

Igmat commented Sep 25, 2018

@mbrowne thanks, and I already know that and even more - use it in my own libraries.
I do use private/protected from TypeScript for declaring separate public interfaces for plugin-authors and usual developers, but unfortunately - it's not enough, especially with meta-programming.

@doodadjs
Copy link

@Igmat From my understanding, it's all about "hard private" (the sigil) vs "soft private" (the "private" keyword). The first ensures that the private keys are not programmatically discoverable, while the latest can't. While we can analyze source code to get the privates keys and plan our attacks, the community has opted for hard private.

@rdking
Copy link
Author

rdking commented Sep 26, 2018

@doodadjs Ignoring TS's private = soft private for a moment:
ES is not TS, so there's nothing in ES that requires it to follow TS's particular use of the private keyword. The fact that it's a reserved word in ES means that ES can use it or not at any point in its future without respect for how it is used in other languages. TS is indeed another language. So there's nothing stopping ES from using private = hard private. TS would simply need to adjust. Not an ES issue.

@bakkot Though I don't ever expect it to happen, I would really like to sit down with you some day and discuss with you why the opinions which you espoused in your FAQ are not the end-all and be-all of whether or not we should use private or # for private fields. Your argument boiled down to:

private implies this.x access semantics, but this.x is public.

I understand this and agree... to a point. It's not the first oddity of that type in ES and it likely won't be the last either. There are similar inconsistencies around the primitive wrappers when compared with Symbol (which doesn't allow new). There's also the fact that [] implies Array involvement yet { foo:'bar' }['foo'] will return "bar" just like you'd expect even though no Array was involved. Like I said before, ES is its own language. When borrowing concepts from other languages, adoptability and the learning curve will suffer less if we remain as close as reasonably possible to the known usage of the concept being borrowed. As such it makes more sense to use keywords since:

  1. Everywhere else in ES, non-keywords never trigger declaration. It's always
    • var <identifier>
    • let <identifier>
    • function <identifier>
    • class <identifier>
    • or just <identifier>
  2. Keywords are the primary method of declaring private fields in the languages that support the concept and style being borrowed.
  3. Punctuation characters (like #) are historically used as operators, not keywords in C-like languages (of which ES is one).

So to you and others, I'm going to suggest that you please quit relying on the FAQ. Those who are making this argument to you have already read it and don't agree that the opinion you expressed is in the best interest of the language and its users.

@ljharb Please quit harping on ES not having access levels. That's like saying binary doesn't support the digit 2. While very much true, that doesn't mean the functionality can't be supported (2(base 10) = 10(base 2)). I used binary because this is very apt for what's being requested. What we're asking for can easily be implemented.

If we consider .# to be an operator that retrieves a separate object that cannot otherwise be reached, and allow that other object to have its own private and public interfaces, then protected is just the public interface of the private container. This automatically means that it is only accessible from methods of instance objects that have access to the private container. Put simply:

var obj = {
  priv: {
    priv: {/*private data*/},
    pub: {/*protected data*/}
  },
  pub: {/*public data*/}
}

and inheritance looks like

var obj2 = {
  priv: {
    priv: {/*private data*/},
    pub: {/*protected data*/, __proto__: obj.priv.pub}
  },
  pub: {/*public data*/, __proto__: obj.pub}
}

Side Note:
I still think .# is just too awkward and causes too many unnecessary usage pattern disruptions. So I still would like to see it reversed as #., which leaves open #[] and not only restores most of the usage pattern difficulties, but also restores an expected implication by causing private x to map to this#.x where the sigil simply means we're accessing stuff on the object to the left that's not available to the public.

@ljharb
Copy link
Member

ljharb commented Sep 26, 2018

@rdking i bring up the point about access levels because people keep talking about protected, which isn't a keyword i'll ever be comfortable adding to the language ¯\_(ツ)_/¯

@rdking
Copy link
Author

rdking commented Sep 26, 2018

@ljharb I somewhat understand your discomfort.
However, the use case described by @Igmat is just one of the uses. There are other use cases that involve the base class being an entirely partial implementation that depends on derived classes to supply specific non-public functions to complete the implementation. The only way that's possible given the current proposal is if the private name of those functions is already known to the base class. Otherwise, the base class will not be able to call derived class members. That's only possible if the base class defines private fields that the derived class can set to the corresponding functions.

Doing this non-publicly is simply not possible given the current proposal without resorting to the implementation of a pattern that is likely less than straight forward for most users. It's not something I would put in a node module and just expect people to understand how to use. If the results of that pattern can be hidden behind a keyword like protected, then it would be far easier to gain adoption.

@ljharb
Copy link
Member

ljharb commented Oct 2, 2018

@Igmat that's a node implementation detail, and not part of the language. Separately, as long as your decorated class runs first, then it'd be impossible to hijack that later - in JavaScript in general, if your code doesn't run first, there's a number of things you can't defend against, but that doesn't apply to modules.

@mbrowne
Copy link

mbrowne commented Oct 3, 2018

This might have been proposed already, but I had an idea: what if the syntax were:

private #x

At first this seems redundant, since # is already a requirement for private fields, so why require the private modifier as well? The value of this becomes apparent when you consider that other access modifiers like friend could be added in the future. That way # wouldn't be a symbol that only applied to private fields - I think @rdking's best point is that coupling # so tightly with only hard private fields will limit syntax options for other access modifiers in the future. This is significant because without a special syntax for accessing non-public fields, it leaves the language/environment no choice except to add overhead to every property lookup (to check if access should be allowed) even though the vast majority of such lookups will be public. In a way, it doesn't matter as much how the fields are declared (decorator syntax could be fine, for example), but the interpreter does need to see some special syntax to differentiate between public and non-public at the place in the code where the field is being accessed (the call site). So here's a fuller example showing the idea:

class Demo {
  private #x
  protected #y
  friend #z
  
  test() {
    this.#x = 1
    this.#y = 2
    this.#z = 3
  }
}

I think the biggest downside of my proposal is the question of how you handle this:

private x

It would either have to be a syntax error, which I'm sure would be confusing to people, or do something special other than hard private -- which might be even more confusing.

So I'm not sure if this is an improvement on this current proposal, but I do think it has a big advantage in terms of future extensibility to other access modifiers.

@bakkot
Copy link
Contributor

bakkot commented Oct 3, 2018

This might have been proposed already

See tc39/proposal-private-fields#53.

Though there might be other reasons to revive that syntax - in particular if we wanted to allow declarations to occur outside of classes.

I think the biggest downside of my proposal is the question of how you handle this:

It would absolutely be a syntax error.

@rdking
Copy link
Author

rdking commented Oct 3, 2018

private #x would also be preferable to the existing syntax because it allows # to be well defined where currently it is not.

@ljharb
Copy link
Member

ljharb commented Oct 3, 2018

What does “well defined” mean to you? In either case it would be precisely and identically “well defined” in the grammar.

@mbrowne
Copy link

mbrowne commented Oct 3, 2018

@dalexander01 your last comment on tc39/proposal-private-fields#53 was from 2016, so I'm curious to know: are you still a proponent of the private #x syntax? Having read through that thread, I'm pretty sure I prefer it to the current proposal of just using #x by itself, for the reasons I mentioned above (which are basically the same reasons you brought up 2 years ago).

@rdking
Copy link
Author

rdking commented Oct 3, 2018

With the understand that programming is little more than a variation of applied math:

In mathematics, an expression is called well-defined or unambiguous if its definition assigns it a unique interpretation or value.

In the case of a programming language, "unique interpretation" is more apt than "unique value". The problem I've been having with this # is that, at least in this proposal, no unique interpretation has been provided. And indeed, none appears to be forth coming. Every attempt I've made to garner a unique interpretation of the meaning for # has failed due to what I can only surmise is blatant obfuscation.

Nothing else in ES suffers this lack of "unique interpretation". Within a given context, every token can be uniquely placed in one of the following categories:

  • Keyword
  • Operator
  • Literal
  • Separator
  • Identifier
  • Terminator

Any arguments contrary to this statement should be placed in #133. Not here. The simple fact that # is so poorly defined is (imo) part an parcel of the problem that is preventing any attempt to come to a rational conclusion about a path forward for shared private fields that doesn't result in a violation of hard private.

@nicolo-ribaudo
Copy link
Member

If one day ECMAScript will support other access modifiers (protected, friend, ...), it will be possible to allow optional private and public before #x and x.

As for what # is, I think that "# is a sigil which introduces the name of a private property" could be a good definition.

@rdking
Copy link
Author

rdking commented Oct 3, 2018

If one day ECMAScript will support other access modifiers (protected, friend, ...), it will be possible to allow optional private and public before #x and x.

"Train up a child in the way he should go, And when he is old he will not depart from it." Prov. 22:6

Why did I quote the Bible? Simple. Even in ancient times, they knew that people stick with the patterns they've grown accustom to. It will do little to no good to "allow optional private and public" once developers have gone so long without it. In fact, the TC39 would be hard-pressed to even consider it given the standing precedent. So if it's not introduced from the beginning, it's pretty much a lost cause.

@mbrowne
Copy link

mbrowne commented Oct 3, 2018

@nicolo-ribaudo The currently proposed syntax would establish in developers' minds that # means private. Subsequently introducing the ability for # to refer to protected or internal fields would be confusing at best. In the absence of modifiers other than private, people would still mentally associate # with private to some degree, but the presence of the keyword mitigates this and I think it would be better than having this inconsistency in the language in the long term. And then there's this quote from @ljharb from the older thread about this:

The general sense was that the worst scenario was if it was optional - it should either be required or forbidden.

...presumably because the inconsistency would be confusing.

The reason I am changing my tune is that it's dawning on me that if this proposal moves forward as-is, native support for something like protected or friend might never be feasible. Several committee members have said otherwise and I have believed the same for a long time, but I am starting to doubt that. What are the odds that if the current proposal is implemented, the committee would be on board with introducing protected #x or friend #x down the line? I'm sure that the syntactic inconsistency and obstacle of retraining developers that # doesn't only mean private would be major objections. In contrast, private #x would pave the way for other access modifiers much better.

And regardless of that, I certainly think it's very unlikely that something like the following unprefixed example would ever gain community or committee support (and I myself would probably be against it if it meant reduced performance for all property lookups):

class Demo {
  #x
  protected y
  friend z
  
  test() {
    this.#x = 1
    this.y = 2
    this.z = 3
  }
}

And beyond those two options, I don't see any possibility of sharing private fields except for workarounds using decorators. In the case of reflection (e.g. soft private) I think that would be fine, but I think it would be really suboptimal for protected/internal/friend/etc.

@mbrowne
Copy link

mbrowne commented Oct 7, 2018

Just in case my fears are unfounded, does anyone think the following syntax would be realistic and viable at some point in the future:

class Demo {
  #x = 1
  protected #y = 2
  friend #z = 3
  ...
}

(Feel free to substitute protected and friend with whatever intermediate access level(s) you think would be appropriate.)

I find the inconsistency problematic, but maybe the committee disagrees. If we don't see the above syntax as viable but still don't want to change it to private #x to make it more consistent, then we should just be realistic about the fact that we will probably be stuck with the limitations of decorator workarounds for good (for private state sharing)...

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Oct 7, 2018

I don't find it too bad: #foo is a private property, but I can add access modifiers to modify its privacy level.

It's the same I consistency that we would have without #:

class Foo {
  x = 2; // <-- no keyword
  private y = 3;
  protected z = 4;
}

@rdking
Copy link
Author

rdking commented Oct 7, 2018

I think the inconsistency is a bit jarring. In #136, I finally figured out how to point out the core inconsistency in this proposal that makes # necessary. So, if we're lucky the TC39 may revisit the need for #. If they decide it's unnecessary given the new arguments, then maybe we won't be stuck with any of this awkward, untenable notation.

@littledan
Copy link
Member

As discussed upthread, we can consider protected as a follow-on proposal (modulo @ljharb 's oft-repeated opposition). At this point, implementations are moving forward with the private fields proposal in this repository; see the README for details. So it's a bit late to expand the scope of this proposal right now.

@dalexander01
Copy link

@dalexander01 your last comment on tc39/proposal-private-fields#53 was from 2016, so I'm curious to know: are you still a proponent of the private #x syntax? Having read through that thread, I'm pretty sure I prefer it to the current proposal of just using #x by itself, for the reasons I mentioned above (which are basically the same reasons you brought up 2 years ago).

@mbrowne yes, I still am a propoent of the private #x syntax for all the reasons indicated in the previous issue.

@littledan
Copy link
Member

Most of the people I've talked with about private #x have found it to be excessively verbose and redundant, once they got the idea of what # means.

@mbrowne
Copy link

mbrowne commented Oct 11, 2018

Thanks for the explanation, @littledan. I suspected this might be the objection to the syntax, but wasn’t sure even after reading all the comments on the old thread why the issue was closed without really explaining why, so it’s good to be clear on why this was rejected. Were the people you talked to who thought it was redundant aware of its implications for the possible addition of other access modifiers in the future? I ask because I would have had the same initial reaction (and for a long time didn’t have any concerns about the absence of a private keyword), and it was only after I thought about it more and the fact that protected or internal fields would probably also need to be accessed with # (regardless of how they’re declared) that I became concerned about it.

Having said that, I can understand a general aversion to avoidable verbosity and I can also imagine some people finding this confusing. So I’m not going to raise a strong objection here, but I will say one more time that I hope the committee has thought about more access levels other than hard private and public and will be open to the idea of adding native support for these in the future if decorator solutions turn out to be unsatisfactory. So that means being open to the syntax that I think was summed up well by @nicolo-ribaudo as being not “too bad” - so not good, but perhaps acceptable.

But among so many objections from other people from the community participating in discussions here, I’d like to end on a positive note. I very much look forward to the availability of private fields, and despite my concerns I think the currently proposed syntax and semantics will still be very usable. More transparency on the goals and hard-line requirements for the proposal would have been really helpful and hopefully you can still offer more of that for questions still unanswered. Nonetheless (as you are probably aware), I don’t think the overwhelmingly negative response here is representative of how the community as a whole will respond when this is actually released, at least not after people read why the syntax choice makes sense and get over any initial aesthetic reaction. Clearly there’s no way to make everyone happy here and some people will always feel that a very wrong decision was made, but IMO the committee should be commended for a huge amount of work and patience over a period of years on this proposal, and engaging with the community in a very open and public forum.

@hax
Copy link
Member

hax commented Oct 12, 2018

Mostly I agree protected is not must to have (at least in current situation) because you can use _x convention for that purpose, though it has some inconvenience that tools can only work speculatively (for example, some tools may treat _x as private not protected). But I hope @ljharb (as always 😛) could use his empathy and try to understand the concerns of others.

@littledan

we can consider protected as a follow-on proposal

In theory we always can postpone it to follow-on proposal, but we should notice current proposal leave a very limited design space of syntax for protected or similar feature!

Because this proposal already chose sigil, it will be very strange and inconsistent to introduce keyword protected without public and private, and protected #x syntax has a very bad mental model (how a modifier can relax the constraint of #priv which is guaranteed to be private, secure, undetectable?). So we are forced to use some much strange sigil based syntax like #-x?? I don't know.

@hax
Copy link
Member

hax commented Oct 12, 2018

@mbrowne

I think the currently proposed syntax and semantics will still be very usable

Technically speaking, I agree with you, this proposal is "usable". But consider already have many other privacy solution in the wild, an "usable" but not convinced proposal will just add another sideway to the divergence. It's harmful to the community. Your follow assumption (about how community will respond) is too optimistic.

Copy from my other comment:

Obviously there are many divergence in what is "look like JavaScript". At least, I hope you guys in the room could accept the truth: most JS programmers think #x is very strange in first place. Some hate it. Whether they can accommodated it is very uncertain. Consider there are already many workable privacy solutions (TS private, _x convention, symbol based private, old closure-based private...), it have a big risk that part of the community will refuse to move to this real privacy and cause the community break, there will be conflicts in the teams and open source projects (for example, refuse to accept PRs which use new feature to refactor the existing code), what worse is, such conflicts will be emotional. I think none of us want another new holy war.

@littledan This is the essential reason I am against this proposal even without consider the existence of a better alternative like classes 1.1 .

@trusktr
Copy link

trusktr commented Nov 19, 2018

It seems that symmetry is the main reason for the # syntax. Well, symmetry is perfectly doable with the public/protected/private keywords too. For example:

https://gist.github.com/trusktr/3088a7a301d96f099e2a0d1aed1403a2

Honestly I don't think it will be hard to learn to use the keywords symmetrically.

@trusktr
Copy link

trusktr commented Nov 19, 2018

We try to design the language such that people can use it comfortably without getting into the details and also such that people who want to get into the details can do so effectively.

@bakkot That's a nice goal. That said, JS doesn't have to strictly be a follower and fit in with other languages. It can, for example, have private x; and private this.x for symmetry and be an innovator and stand apart from the crowd. I'm certain people capable of learning to program are capable of learning the symmetry of the key words. They may experience an error on their first try, but they'll remember to use the specifier the next time; no biggie.

Learning types in TypeScript, for example, is a much bigger endeavor than learning to use access keywords symmetrically as in the above example (ignore semantics, rules, and functionality, I'm only referring to using the keywords symmetrically).

@mbrowne
Copy link

mbrowne commented Nov 19, 2018

@trusktr Given that you're a proponent of protected, this means you'd need to change all the code that accesses your protected property as well as the declaration if you wanted to change it to private, or vice versa. That's kind of a minor issue; for me the bigger issue is that your proposed syntax is just needlessly verbose, for no gain as far as I can tell. Also, although I'm not sure if your exact syntax has been proposed before (although it seems likely that it has...I haven't followed every discussion but have seen tons of variations in the discussions I have paid attention to), but see also #100 (comment).

@trusktr
Copy link

trusktr commented Feb 4, 2019

Given that you're a proponent of protected, this means you'd need to change all the code that accesses your protected property as well as the declaration if you wanted to change it to private, or vice versa. That's kind of a minor issue;

Seems that the same thing applies for switching from public to private, but yeah, that's not a good reason not to have private with a sigil. The same applies with async/await: you add it to a function called by other functions, then the whole stack needs to be converted (or at least handle the Promise return). But that makes the code explicit and less likely to have bugs at the expense of a little more work. I think the same is fine with protected/private.

for me the bigger issue is that your proposed syntax is just needlessly verbose, for no gain as far as I can tell.

It may be more verbose, but if it were implemented in some sort of way like with "access modes", then it'd be interoperable with all the patterns we use today on normal objects, and all the libs like underscore, lodash, etc.

I'm just trying to think about making it work well with what we already have. Private fields is a another thing in an of itself which are not properties, and do not work with existing tools, not even the built-in ones like Object.keys, etc.

@trusktr
Copy link

trusktr commented Feb 4, 2019

#100 (comment) has more upvotes than downvotes, because those people that upvoted want the same sort of dynamicism which the current private fields do not offer (if that's not it, then they just like the verbosity alone).

Personally, I think offered functionality of a feature is much more important than the amount of keystrokes we save with the feature.

I really don't mind typing "private" if I get all the extra dynamicism that @bdistin enumerated in the table.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests