Protected and friend access modifier for class members

Private static and instance properties/accessor properties/methods of classes in Javascript are currently at stage 3 of the TC39 standardization process (see here). My idea is based on that work. Most OOP languages (e.g. Java, C++ and even TypeScript) do not only have public and private access modifiers but also a kind of protected access modifier which is currently missing in Javascript/ECMAScript.

As Javascript uses the # character to indicate private members of classes and no access modifier at all for public access to members of classes, it seems not to be a good idea to use a protected keyword for protected members of classes in Javascript as it is used in Java, C++, or TypeScript. Furthermore, there are some ideas and a proposal (see ideas for use of class static blocks, proposal) to extend the normal use case of protected class members (restrict access to the class and its subclasses) to be able to access them from unrelated classes or even external functions which are not related to the class.

I think that a general principle should be kept: A class should control which external entites are allowed to access class members with restricted access modifiers, and all the logic, code, and data concerned to access restrictions should be part of the class itself. Therefore, no external variables should be needed to get access to class members with restricted access modifiers as it is used in the examples of the ideas and the proposal mentioned above. The principle is important as it keeps an OOP mindset.

My idea is two-fold:

  1. Introduce a new character for the beginning of an identifier name to signal protected access for the corresponding class member similar to the protected keyword in other OOP languages (e.g. Java, C++). As Javascript already uses many of the characters available on normal keyboards, and to prevent ambiguous character reuse, I suggest the use of the § character for protected class members. Other suggestions are welcome.;
  2. Open access to protected and private class members for unrelated "friend" classes. As they are "friends", I want to suggest to introduce the keywords friend and friends but that is only one option which can be substituted.

To clarify my idea, I want to give same examples:
a) Use of identifiers beginning with an § character to define protected class members (open access to class members of the base class A for subclass B):

class A {
  §x = 0;
  get §valueX() { return §x; }
  set §valueX(x) { §x = x; }
  §addToX(value) { §x += value; }

  static §sx = 1;
  static get §valueSX() { return §sx; }
  static set §valueSX(sx) { §sx = sx; }
  static §addToSX(value) { §sx += value; }
}

class B extends A {
  instanceAccess() {
    §x++;             // access to #x of base class A 
    §valueX += 2;     // access to get/set #valueX() of base class A 
    §addtoX(4);       // access to #addToX(value) of base class A
  }

  static staticAccess() {
    §sx++;             // access to #sx of base class A 
    §valueSX += 2;     // access to get/set #valueSX() of base class A 
    §addtoSX(4);       // access to #addToSX(value) of base class A
  }
}

b) Use of the keyword friends and friend to open access to class members of class A for an unrelated class C:

class A friends C {
  // private instance members become friends
  friend #x = 0;
  friend get #valueX() { return #x; }
  friend set #valueX(x) { #x = x; }
  friend #addToX(value) { #x += value; }

  // protected instance members become friends
  friend §y = 0;
  friend get §valueY() { return §y; }
  friend set §valueX(x) { §x = x; }
  friend §addToX(value) { §x += value; }

  // private static members become friends
  friend static #sx = 1;
  friend static get #valueSX() { return #sx; }
  friend static set #valueSX(sx) { #sx = sx; }
  friend static #addToSX(value) { #sx += value; }

  // protected static members become friends
  friend static §sy = 1;
  friend static get §valueSY() { return §sx; }
  friend static set §valueSY(sx) { §sx = sx; }
  friend static §addToSX(value) { §sx += value; }
}

class C {
  instanceAccess() {
    const objA = new A();
    objA.#x++;             // access to #x of unrelated class A 
    objA.#valueX += 2;     // access to get/set #valueX() of unrelated class A 
    objA.#addtoX(4);       // access to #addToX(value) of unrelated class A

    objA.§y++;             // access to §y of unrelated class A 
    objA.§valueY += 2;     // access to get/set §valueY() of unrelated class A 
    objA.§addtoY(4);       // access to §addToY(value) of unrelated class A
  }

  static staticAccess() {
    A.#sx++;             // access to #sx of unreleated class A 
    A.#valueSX += 2;     // access to get/set #valueSX() of unrelated class A 
    A.#addtoSX(4);       // access to #addToSX(value) of unrelated class A

    A.§sx++;             // access to §sx of unreleated class A 
    A.§valueSY += 2;     // access to get/set §valueSY() of unrelated class A 
    A.§addtoSY(4);       // access to §addToSY(value) of unrelated class A
  }
}

Other considerations for protected methods:
a) Protected methods should be overwriteable in subclasses;
b) The access modifier of a method in a subclass which overrides a protected method of a base class needs to be at least as accessible as the base class method;
c) Public methods of a base class must not be overriden by a protected method of a subclass;

Other open questions:
a) Are there alternative characters to §?

A small side-note. This character is not typeable from Windows keyboard. Even my external mac keyboard (US layout) is devoid of it. This might be an obstacle with adapting this.

1 Like

JavaScript does not have access levels - something is either reachable/public or unreachable/private.

“protected’ doesn’t make sense in the language without a way to limit access to things that’s different from lexical scope, and that doesn’t require circular dependencies. The challenge for this concept isn’t the syntax or keyword used to denote it; it’s “what could the semantics possibly be”? I remain convinced that it’s not achievable in an ergonomic way.

3 Likes

Many other languages have had the protected keyword, and have had it for a while. It goes back to a time when OOP was all the rage, and inheritance was being used left and right. Since then, we've learned that we were overusing inheritance, and in general, it's better to favor composition over inheritance.

As OOP evolves, we need to figure out which features from these original OOP languages were good ideas, and which ones were not-so-great but got propagated around anyways. I don't know where "protected" and "friend" falls in all of this, but I know a number of newer languages don't include the classical idea of inheritance at all - Rust, for example, uses protocols instead (which is also a current ecmascript proposal).

2 Likes

I am not an implementer of the language, and I only read a very small part of the specification, but isn't is possible to implement protected access the same or a similar way than it is implemented for private access?

The semantics of protected access is: Access to a protected member of a base class from outside the class is not allowed except for subclasses of the base class.
As I suggest to use special identifier names which start with a character not allowed in the current version of Javascript. isn't it possible to use the lexical scope to restrict access similar to way private access is implemented? What prevents an efficient implementation in that case?

javascript is primarily a "glue" language to message-pass dynamic [string] data between ui-elements and databases/filestores/wasm-sqlite.

things like protected (or even private) members offer little ergonomics to users like me who employ it in the above-stated message-passing role.

Yes, I know that OOP can lead to difficult dependancy trees or even dependency graphs (C++), but the most in demand programming languages nowdays (see TIBOE-index) are still OOP languages like C++, Java, Python, C# and maybe Visual Basic. If these languages would not be useful any more, why are they so favoured?

Of course, one should not be forced to use OOP or restricted to OOP, but there are situations where OOP can help and makes sense. I don't have a dogmatic but pragmatic mindset, and from a pragmatic point of view I must ask me why are classes and inheritance introduced to Javascript? Isn't it the use of OOP? Doesn't the ability to use protected members in classes offer more possibilities for developers? Nobody is forced to use protected access in classes but it can help others.

Yes, I know that Rust does not have classes but only traits which are similar to interfaces in Java. Traits offer trait inheritance and polymorphics (I think). Therefore, it is missing completely the ability to have a static part like it is possible in classes, and interfaces normally do not offer code reuse what classes provide. That is the reason why I do not use Rust until kniow. I was very interested in Rust but after one day I gave up because I did not find anywhere classes in Rust. I even was frustrated and disappointed after that.

I am sorry that Your keyboard does not have a § character but § was only a suggestion and can be substituted.

Don't get me wrong - I'm not trying to bash on OOP, nor say it's useless these days. I'm just saying it's changing, and I'm honestly interested in having an open conversation about weather or not there's still value in having these kinds of assessors that you're suggesting, or, if some of these newer languages who are trying to provide OOP with a different angle are onto something. (they might not be - Rust's trait model could very well just be a failed experiment. Or, maybe they do have something worthwhile to glean from).

You don't need to use protected or private access modifiers but others may be interested in using them. Furthermore, private access using the # charater is already implemented in V8, and is offered by current versions of Node.js, Electron, and even TypeScript 4.3 will have Javascript private access syntax and semantics. Protected access even offers more possibilities, and everyone is invited to use these possibilities but nobody is forced to it.

Also, I would never think of anyone as having a dogmatic mindset because they believe a certain way. I feel like we programmers use the term "dogmatic" in almost a derogatory way to belittle anyone who believes something different from us, and our belief is not mainstream. The fact that you even have to defend yourself and make it clear that you've come to these decisions on your own accord, and are not just following the crowd is a little sad.

I'll get off of my little soapbox about that now.

This has been repeated many, many times, but no, JavaScript is absolutely and objectively not primarily a "glue" language for the purposes you describe. Please stop claiming that.

4 Likes

The mistake being made here is seeing "public and private" and assuming that "protected" makes sense. In other languages with these three "access levels", the terms "private" and "public" don't have the same meaning as they do in JS.

The only mechanism that exists in JS for privacy is lexical scoping, and there is only one degree of privacy: 100%, or 0%, reachable or unreachable. Private access is effectively specified/implemented with the exact same mechanisms as "closed-over variables" are - lexical scoping. I do not believe it is possible to implement "protected" in a remotely reliable way - I think the concept is flatly inappropriate for this language.

6 Likes

not primarily a "glue" language for the purposes you describe.

yes it is. have you noticed the dwindling interest in hiring "general-purpose" javascript programmers these days? employers are instead looking for reliable product-developers -- ppl who ultimately use javascript as a tool to message-pass dynamic-data between ui, database, filestores.

Maybe part of the issue here is that "protected" in javascript would be such a week guarantee anyways.

With private fields, they were able to make it so those fields were only accessible within the class block. Even functions that were attached to the class's prototype after the fact can't access private fields.

But, how can you provide any level of guarantee for protected fields? Here is a rough example of how I might be able to extract protected fields from an instance, no matter how this is implemented. (assume the _secretValue is meant to be protected)

class Base {
  _secretValue = 'mySecret' // Assume this is protected
}

class Derived extends Base {
  // This class uses _secretValue properly
}

// elsewhere ...

const derivedInstance = new Derived()

function ProtectedExtractingClass () {}
ProtectedExtractingClass.prototype = derivedInstance
class ProtectedExtractingClass2 extends ProtectedExtractingClass {
  getValue() {
    return this._secretValue
  }
}

console.log(new ProtectedExtractingClass2().getValue()) // shows "mySecret"

No, I have not noticed that, and I would appreciate if you would avoid using anecdata to marginalize valid and important use cases. I believe you that this is your experience, but your experience is not universal.

So, private class members are implemented using the lexical scope, and as the private identifier name is in the scope of the class, access is granted. Accessing the same class member from outside of the class using an instance of the class or the class name (static members) errors because the identifier is not in the lexical scope of the current expression. Is that right?

On the other hand, access to inherited class members from a subclass has nothing to do with the lexical scope, or? It uses the concept of the prototype chain, and in internal algorithms walks up the chain to find matching class member of a superclasses. In the case of a match, access is granted. At least this is the case for methods but not for class data (i.e. class member variables). Therefore, an expression obj.#method() does not make use of the prototype chain, or is #method() not part of the prototype chain?

Anyway, my idea of protected access uses special identifier names similar to the special names for private class members. Therefore, they are not accessible outside of the class similar to private class members but cannot protected methods of a class be part of the prototype chain? And if a subclass tries to access a protected method, cannot the algorithm try to find the method in the prototype chain similar to public methods? Isn't that as efficient as using public base class methods from within the subclass?

What is with this case:

class A {
  #x = 0;
  method(obj) { return obj.#x; }
}
new A().method(new A());

Here, the access obj.#x is allowed though only obj is in the lexical scope but not obj.#x, or is it different? Sorry, but I am not an expert on the internal algorithms used.

The prototype chain is fully public, and anything that’s inherited is also thus fully public - because otherwise, without lexical scoping, JavaScript has no way to provide that access.

In js, subclasses can be created at any time, which almost certainly means that i can subclass your class and use that to access the protected data of your class instances, which sounds pretty unprotected to me.

1 Like

If You want to share a secret between two classes You could use the concept of friend classes together with private class properties which I mentioned at the top of this suggestion. Other classes would not be able to disclose that value. Unfortunately, the implementation of that feature could be difficult ...

In js, subclasses can be created at any time, which almost certainly means that i can subclass your class and use that to access the protected data of your class instances, which sounds pretty unprotected to me.

You are right, but the same is valid in other OOP languages, I think, but there may exist the concept of final classes which cannot be derived. On the other hand, TypeScript has public, protected and private access modifiers, and there one can derive from a class the same way as in Javascript. Therefore, protected offers not more protection in TypeScript, too.