javascript-private-state icon indicating copy to clipboard operation
javascript-private-state copied to clipboard

Why is the # needed?

Open vanwagonet opened this issue 10 years ago • 22 comments

What are the problems preventing the following syntax, and requiring the introduction of #?

class Privateer {
  private flag = 'GB';
  constructor (flag) {
    this.flag = flag
  }
  getCurrentAllegiance () {
    return this.flag
  }
}

vanwagonet avatar Nov 18 '15 20:11 vanwagonet

See this https://github.com/zenparsing/es-private-fields/issues/14 for an explanation of the issues.

zenparsing avatar Nov 18 '15 20:11 zenparsing

That makes sense. I still think that omitting the this in favor of a plain #flag is inconsistent with the rest of the language as it currently stands. The private lookup is a (private) member lookup, not a plain variable reference, and would preferably always be part of a MemberExpression.

Perhaps this.#flag or this[#flag] would be less alien.

vanwagonet avatar Nov 18 '15 21:11 vanwagonet

I'm not so sure about having the # as part of the name either. It seems like

class Derived3 extends Base {
  // adds an additional private slot that hides inherited slot2
  private #slot2;

  getData1() {
    // returns undefined since subclass slot2 was not initialized
    return #slot2;
  }
}

could just be

class Derived3 extends Base {
  // adds an additional private slot that hides inherited slot2
  private slot2;

  getData1() {
    // returns undefined since subclass slot2 was not initialized
    return this.slot2;
  }
}

On the other hand, I do think it might be nice to replace the accessor operand (.) with another (#). For example:

class Derived3 extends Base {
  // adds an additional private slot that hides inherited slot2
  private slot2; // <-- clearly private

  getData1() {
    // returns undefined since subclass slot2 was not initialized
    return this#slot2; // <-- "#" instead of "." which denotes private access.
  }
}

so that when reading code it is clear that it's a private access, but the name of the variable isn't thought to include the #. But, then again, how do you say that? this.foo is this dot foo but how do you say this#foo? "this pound foo"? "this num foo"? I like the readability though. It's like replacing the common practice of this._foo with an official this#foo.

trusktr avatar Dec 10 '15 03:12 trusktr

And, what about for protected members? Maybe we can use a different symbol for that? Here are some with symbols that currently make SyntaxErrors: this@foo, this!foo, this~foo. There's some real-estate left.

Or, maybe we can use one or two of the same symbol to denote protected and private, respectively:

this#foo // protected
this##foo // private

this@foo // protected
this@@foo // private

this!foo // protected
this!!foo // private

this~foo // protected
this~~foo // private

We could also combine symbols:

this#foo // protected
this#>foo // private

But, I think my favorite is one of these:

this#foo // protected
this@foo // private

this#foo // protected
this~foo // private

this#foo // protected
this!foo // private

The exclamation kind of helps the notion of private, as in "watch out, this is sensitive data!".

None-the-less, I could live with a single symbol (this#foo) for both protected and private, but having two forms could be beneficial for readability. I could also just live with this.foo with errors thrown when I access something I'm not supposed to access, like in Java, where private, protected, and public members all blend together in terms of readability.

trusktr avatar Dec 10 '15 03:12 trusktr

For private methods, the same could apply:

class Foo {
  private foo
  protected bar
  constructor() {
    this!foo = 'foo'
    this#bar = 'bar'

    this#doSomething()
    this!doSomethingElse()
  }
  protected doSomething() { /* ... */ }
  private doSomethingElse() { /* ... */ }
}

trusktr avatar Dec 10 '15 03:12 trusktr

I forgot one symbol, the colon (:). Both : and ! have a period (.) in them, so they might play well together (.:!, they escalate):

this.foo
this:foo // protected
this!foo // private!

The only thing about the colon is that it might conflict with labels. But who uses those? Plus, it's easy to differentiate between a label for a loop and obj:foo.

class Foo {
  private foo
  protected bar
  constructor() {
    this!foo = 'foo'
    this:bar = 'bar'

    this:doSomething()
    this!doSomethingElse()
  }
  protected doSomething() { /* ... */ }
  private doSomethingElse(otherFoo) {
    console.log(otherFoo:bar)
    console.log(otherFoo!foo)
  }
}

trusktr avatar Dec 10 '15 03:12 trusktr

I may have missed why one of the above symbols can't be used. If so, just ignore that one.

trusktr avatar Dec 10 '15 03:12 trusktr

Oh, note that this.#foo would be one of those two-symbol combinations, and that I'd be fine with that but while using this.#foo but not simply #foo, as that seems more inline with the current language.

trusktr avatar Dec 10 '15 03:12 trusktr

Other possibilities:

this.foo
this.:foo // protected
this.!foo // private!

// or 

this.foo
this.#foo // protected
this.!foo // private!

while the declaration is still just

private foo
protected foo

without extra symbols (since the private and protected keywords are already there).

trusktr avatar Dec 10 '15 03:12 trusktr

I think, after all this, I kind of like that last idea most, the augmentation of this. by adding an extra symbol, so it can still read "this dot foo", but with a visual clue as to what type of access it is.

trusktr avatar Dec 10 '15 03:12 trusktr

@trusktr So drop protected and just have this.@foo. Done! : )

zenparsing avatar Dec 10 '15 03:12 zenparsing

@zenparsing But, I do like the idea of protected members. I find myself wanting those often.

There is this package mozart which has protected members implemented just about as well as it can possibly be done in current JavaScript, but then we have to use the library everywhere in a code base if we want consistency, it has performance considerations, etc, so it has it's downsides.

trusktr avatar Dec 10 '15 04:12 trusktr

@zenparsing this.@foo looks much much better than #foo imho. It's obviously a member expression. It's also obvious that the member is private, and so the lookup rules are different.

A .@ operator is also unambiguously different from the @ decorator proposal.

class Privateer {
  private flag = 'GB';
  constructor (flag) {
    this.@flag = flag;
  }
  getCurrentAllegiance () {
    return this.@flag;
  }
}

vanwagonet avatar Dec 10 '15 16:12 vanwagonet

``@` is not looked so good if es7 decorators (or any another annotation-liked syntax) will be also shipped in language:

class Privateer {
  @decotator private flag = 'GB';
  constructor (flag) {
    this.@flag = flag;
  }
  getCurrentAllegiance () {
    return this.@flag;
  }
}

two different functionalities but with similar characters in their syntax is not looked so good. maybe something C++ like ->?

class Privateer {
  @decotator private flag = 'GB';
  constructor (flag) {
    this->flag = flag;
  }
  getCurrentAllegiance () {
    return this->flag;
  }
}

PinkaminaDianePie avatar Dec 15 '15 11:12 PinkaminaDianePie

I thought of -> too, but I never liked it because the - is almost never aligned with the >, which bothers me. x]

trusktr avatar Dec 15 '15 20:12 trusktr

I think the only one I really didn't like was a plain #flag with no this.

vanwagonet avatar Dec 15 '15 23:12 vanwagonet

Adding a random character to mark something private is unneccessary syntax. The compiler can treat it as such as well as any decent IDE.

I suggest marking it as private (or protected), and optionally accessing it with the this keyword.

class Derived3 extends Base {
  // private field
  private slot2 = 'something';

  getData1() {
    // optionally use the this keyword. slot2 is scoped to the class so its available here
    var k = this.slot2;
    var j = slot2;
    assert.areEqual(j, k);
  }

  getData2(slot2) {
    // slot2 is now scoped to this block, but this.slot2 is scoped to the class
    assert.areNotEqual(slot2, this.slot2);
  }
}

This is similar to the compiled languages

carbonrobot avatar Jan 12 '16 15:01 carbonrobot

@carbonrobot

// What does this do?
Derived3.prototype.getData1.call({ slot2: "not-private" });

Without type information which can tell us whether a property name is private or public, we need a syntactically unambiguous way to make that determination.

zenparsing avatar Jan 12 '16 15:01 zenparsing

I would like to say that should be a compiler error, since using call in that way violates everything OOP about classes...but your right, its still supported.

carbonrobot avatar Jan 12 '16 16:01 carbonrobot

This proposal doesn't feel like it fits in with the language; the rules for access feel arbitrary and confusing. In addition, the # prefix feels unnecessary.

I'd prefer that private be semantically similar to a VariableDeclarator (ie. var, let, const), but the scope it defines its identifier as valid within is that of instances of the class (rather than the class itself):

class Foo {
  // The bar identifier is only accessible within this ClassBody,
  // but is unique to each instance of the class.
  private bar;

  constructor(val) {
    bar = val;
  }
}

bar; // ReferenceError

Foo.prototype.baz = function() {
  bar; // ReferenceError
};

I feel that this is a more natural fit for the language, and the rules around it are very simple to understand. If it is backed by private slots, that is fine by me, but that seems like an implementation detail (and therefore not worth introducing # for).

Additionally, static private could define class-level private members, but this is already pretty easy to accomplish with closures, so I'm not convinced it's entirely necessary.

I'm not sure how protected would fit into this model (if at all).

Also, it'd be great if private methods could be defined with the same semantics:

class Foo {
  private doSomeComplexTask(input) { /* ... */ }

  constructor(input) {
    doSomeComplexTask(input);
  }
}

(I actually find myself wanting private methods a lot more than private state, personally.)

suchipi avatar Apr 19 '16 05:04 suchipi

Omitting the this keyword removes the semantical importance of this. Seeing bar = val; could make someone falsely believe that bar is a previously declared variable, possibly from an outer scope; there is added ambiguity. this.bar = val is much more clear. Having some sort of symbol differing from the dot notation makes it even more clear that it's a private access, f.e. this.-bar = val where - means private. Your example would be

class Foo {
  private doSomeComplexTask(input) { /* ... */ }

  constructor(input) {
    this.-doSomeComplexTask(input);
  }
}

where the this.-doSomeComplexTask(input); is not ambiguous. At the very least, I wouldn't mind if private properties (which can contain methods) were used the same as current dot notation (f.e. this.bar), where trying instance.bar from the outside would simply throw an error. The extra symbol (f.e. this.-bar) would be a nice bonus.

On Mon, Apr 18, 2016 at 10:15 PM, Stephen Scott [email protected] wrote:

This proposal doesn't feel like it fits in with the language; the rules for access feel arbitrary and confusing. In addition, the # prefix feels unnecessary.

I'd prefer that private be semantically similar to a VariableDeclarator (ie. var, let, const), but the scope it defines its identifier as valid within is that of instances of the class (rather than the class itself):

class Foo { // The bar identifier is only accessible within this ClassBody, // but is unique to each instance of the class. private bar;

constructor(val) { bar = val; } }

bar; // ReferenceError

Foo.prototype.baz = function() { bar; // ReferenceError };

I feel that this is a more natural fit for the language, and the rules around it are very simple to understand. If it is backed by private slots, that is fine by me, but that seems like an implementation detail (and therefore not worth introducing # for).

Additionally, static private could define class-level private members, but this is already pretty easy to accomplish with closures, so I'm not convinced it's entirely necessary.

I'm not sure how protected would fit into this model (if at all).

Also, it'd be great if private methods could be defined with the same semantics:

class Foo { private doSomeComplexTask(input) { /* ... */ }

constructor(input) { doSomeComplexTask(input); } }

(I actually find myself wanting private methods a lot more than private state, personally.)

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub

trusktr avatar Apr 20 '16 04:04 trusktr

The # symbol should not be a part of a private specification.

nijikokun avatar Aug 10 '16 18:08 nijikokun