New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yet another approach to a more JS-like syntax #28

Closed
zocky opened this Issue Mar 18, 2018 · 71 comments

Comments

Projects
None yet
@zocky

zocky commented Mar 18, 2018

I realize I might be beating a dead horse, but it seems to me that I'm far from being the only one who intensely dislikes the use of # for private fields/methods. So here goes:

  1. Use the private keyword to introduce private field, methods, setters, getters, exactly as with static.
  2. Use bare field and method names without any qualifiers to access private fields and methods within the class's methods, just like we do now in factory functions, i.e. treat them like variables.
  3. Use private.name to explicitly access the private fields of this object, if there are conflicting names, just like we do with super. Also use private["name"] for non-ident and calculated field names.
  4. Use private(otherObject).name or private(otherObject)["name"]to access the private fields of another object of the same class. For symmetry, private(this).x is the same thing as private(this)["x"] private.x or private["x"] or just x if there's no variable x in scope.
  5. In methods, private and private(otherObject) are not valid constructs without being followed by .field or ["field"] and throw syntax errors.

AFAICT, this achieves all the requirements, without introducing a wildly asymmetrical new syntax for private fields, AND gives us a natural way of reading code in human language, solving the naming problem that was brought up on other threads.

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Mar 18, 2018

Member

Use bare field and method names without any qualifiers to access private fields and methods within the class's methods, just like we do now in factory functions, i.e. treat them like variables.

We've been a bit hesitant to go for proposals like this, as it could be seen as a confusing extension to lexical scope. See zenparsing/js-classes-1.1#18 for related discussion.

Use private.name to explicitly access the private fields of this object, if there are conflicting names, just like we do with super.

Similarly, we've been hesitant to add further structures which implicitly reference this, as it's already hard enough for people to figure out this in debugging.

Use private(otherObject).name to access the private fields of another object of the same class. For symmetry, private(this).x is the same thing as private.x or just x if there's no variable x in scope.

In this case, what would be the value of private(otherObject)? Would it require a literal . after it?

One concern I have about this proposal is that, if you forget to reference things through private and just use ., your code will all work (modulo initializers), just that everything will be public. Making # part of the "name" makes this hazard less likely.

Member

littledan commented Mar 18, 2018

Use bare field and method names without any qualifiers to access private fields and methods within the class's methods, just like we do now in factory functions, i.e. treat them like variables.

We've been a bit hesitant to go for proposals like this, as it could be seen as a confusing extension to lexical scope. See zenparsing/js-classes-1.1#18 for related discussion.

Use private.name to explicitly access the private fields of this object, if there are conflicting names, just like we do with super.

Similarly, we've been hesitant to add further structures which implicitly reference this, as it's already hard enough for people to figure out this in debugging.

Use private(otherObject).name to access the private fields of another object of the same class. For symmetry, private(this).x is the same thing as private.x or just x if there's no variable x in scope.

In this case, what would be the value of private(otherObject)? Would it require a literal . after it?

One concern I have about this proposal is that, if you forget to reference things through private and just use ., your code will all work (modulo initializers), just that everything will be public. Making # part of the "name" makes this hazard less likely.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 18, 2018

We've been a bit hesitant to go for proposals like this, as it could be seen as a confusing extension to lexical scope. See zenparsing/js-classes-1.1#18 for related discussion.

IMO, it's no more confusing than the fact that other syntax inside a class definition works differently than outside of it. And for beginners, it's probably less confusing than a closure. Anyway, it is exactly what we most commonly do now with factory functions when we want to have the equivalent of private fields.

Similarly, we've been hesitant to add further structures which implicitly reference this, as it's already hard enough for people to figure out this in debugging.

I don't understand this concern. It's exactly the same as with other things that already exist. How is private.foo() more confusing than super.foo()?

In this case, what would be the value of private(otherObject)? Would it require a literal . after it?

Yes. It's not a function call, it's a construct. And it wouldn't be the only one that looks vaguely like a function call, but actually isn't. We don't expect if(...) or for(...) or while(...) after a do block to work or parse exactly like function calls, even if they look vaguely similar.

zocky commented Mar 18, 2018

We've been a bit hesitant to go for proposals like this, as it could be seen as a confusing extension to lexical scope. See zenparsing/js-classes-1.1#18 for related discussion.

IMO, it's no more confusing than the fact that other syntax inside a class definition works differently than outside of it. And for beginners, it's probably less confusing than a closure. Anyway, it is exactly what we most commonly do now with factory functions when we want to have the equivalent of private fields.

Similarly, we've been hesitant to add further structures which implicitly reference this, as it's already hard enough for people to figure out this in debugging.

I don't understand this concern. It's exactly the same as with other things that already exist. How is private.foo() more confusing than super.foo()?

In this case, what would be the value of private(otherObject)? Would it require a literal . after it?

Yes. It's not a function call, it's a construct. And it wouldn't be the only one that looks vaguely like a function call, but actually isn't. We don't expect if(...) or for(...) or while(...) after a do block to work or parse exactly like function calls, even if they look vaguely similar.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 18, 2018

One concern I have about this proposal is that, if you forget to reference things through private and just use ., your code will all work (modulo initializers), just that everything will be public. Making # part of the "name" makes this hazard less likely.

But you don't actually do that in this proposal. this.x is not the same thing as private.x, and can never be accessed using the same syntax.

zocky commented Mar 18, 2018

One concern I have about this proposal is that, if you forget to reference things through private and just use ., your code will all work (modulo initializers), just that everything will be public. Making # part of the "name" makes this hazard less likely.

But you don't actually do that in this proposal. this.x is not the same thing as private.x, and can never be accessed using the same syntax.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 18, 2018

I've added a clarification above.

zocky commented Mar 18, 2018

I've added a clarification above.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 18, 2018

Another thought - the idea that private and public fields should not be in the same namespace (which I follow in this proposal) isn't necessarily wrong, but it does make a very common use case (change a field from public to private or vice versa) a menial and error-prone task, unlike in other languages where it consists of just adding or removing the private keyword in the class definition.

zocky commented Mar 18, 2018

Another thought - the idea that private and public fields should not be in the same namespace (which I follow in this proposal) isn't necessarily wrong, but it does make a very common use case (change a field from public to private or vice versa) a menial and error-prone task, unlike in other languages where it consists of just adding or removing the private keyword in the class definition.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

I was pleasantly surprised about this other way proposed by @zocky, I hope it will get a lot of attention, as it's a really clever and clean way of solving this issue.

mgtitimoli commented Mar 19, 2018

I was pleasantly surprised about this other way proposed by @zocky, I hope it will get a lot of attention, as it's a really clever and clean way of solving this issue.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

I might agree with avoiding the implicit way of using private members, but for the other way, this is, the explicit one, using private not only for defining, but also for accessing them is a clear goal, as it introduces only one keyword to do everything, and allows using the square bracket notation without having to prefix the identifier with any other symbol which is also something really desirable.

On the other hand, the fact it shares a lot of similarities with how super is used, reduces a lot the learning curve required to make use of this new feature at the same time that it clearly simplifies the amount of stuff needed to teach this to newcomers.

And last but not least, it translates 1 to 1 with a possible "private" function that receives an instance and returns the previously stored "private" properties in a WeakMap, which makes this way super easy to teach and understand as it can be constructed with other existent primitives almost directly.

I'm talking about the following example which could be easily refactored using this new syntax just by removing all the helpers, the constructor (it would be super nice to allow initializers too 🙂), and renaming priv to private.

const privByInstance = new WeakMap();

const initPriv = (instance, members = {}) => privByInstance.set(instance, members);

const priv = instance => privByInstance(instance).get(instance);

export default class MyClass {
  constructor() {
    initPriv(this, {privA: "A", privB: "B"});
  }

  get privA() {
    return priv(this).privA
  }
}

mgtitimoli commented Mar 19, 2018

I might agree with avoiding the implicit way of using private members, but for the other way, this is, the explicit one, using private not only for defining, but also for accessing them is a clear goal, as it introduces only one keyword to do everything, and allows using the square bracket notation without having to prefix the identifier with any other symbol which is also something really desirable.

On the other hand, the fact it shares a lot of similarities with how super is used, reduces a lot the learning curve required to make use of this new feature at the same time that it clearly simplifies the amount of stuff needed to teach this to newcomers.

And last but not least, it translates 1 to 1 with a possible "private" function that receives an instance and returns the previously stored "private" properties in a WeakMap, which makes this way super easy to teach and understand as it can be constructed with other existent primitives almost directly.

I'm talking about the following example which could be easily refactored using this new syntax just by removing all the helpers, the constructor (it would be super nice to allow initializers too 🙂), and renaming priv to private.

const privByInstance = new WeakMap();

const initPriv = (instance, members = {}) => privByInstance.set(instance, members);

const priv = instance => privByInstance(instance).get(instance);

export default class MyClass {
  constructor() {
    initPriv(this, {privA: "A", privB: "B"});
  }

  get privA() {
    return priv(this).privA
  }
}
@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Mar 19, 2018

Contributor

The FAQ is intended to address this proposal, which has come up a couple of times. As it says, the main thing for me is that private x for declaration without this.x for access seems like it would be a giant footgun.

Contributor

bakkot commented Mar 19, 2018

The FAQ is intended to address this proposal, which has come up a couple of times. As it says, the main thing for me is that private x for declaration without this.x for access seems like it would be a giant footgun.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

So @bakkot you find private.x for accessing private members not enough clear?

mgtitimoli commented Mar 19, 2018

So @bakkot you find private.x for accessing private members not enough clear?

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

As it says, the main thing for me is that private x for declaration without this.x for access seems like it would be a giant footgun.

Is this a technical objection, or just a matter of taste?

Edit:
We also declare static fields with static x but can't access them with this.x, but rather have to use this.constructor.x. (It would be nice if we could access them with static.x, but that's a matter for a different discussion.)

zocky commented Mar 19, 2018

As it says, the main thing for me is that private x for declaration without this.x for access seems like it would be a giant footgun.

Is this a technical objection, or just a matter of taste?

Edit:
We also declare static fields with static x but can't access them with this.x, but rather have to use this.constructor.x. (It would be nice if we could access them with static.x, but that's a matter for a different discussion.)

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

As far as I can see, none of the items expressed in the FAQ explores the way expressed here, and in the other hand and IMHO if at the time of designing the way super is used, the committee resolved that's clear and easy to understand, then the mechanisms expressed here should surprised anyone.

mgtitimoli commented Mar 19, 2018

As far as I can see, none of the items expressed in the FAQ explores the way expressed here, and in the other hand and IMHO if at the time of designing the way super is used, the committee resolved that's clear and easy to understand, then the mechanisms expressed here should surprised anyone.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

FTR: I've just shared this issue in JS Classes 1.1 Proposal as IMHO it addresses in a clean and understandable way the main topics that made that other proposal to emerge.

mgtitimoli commented Mar 19, 2018

FTR: I've just shared this issue in JS Classes 1.1 Proposal as IMHO it addresses in a clean and understandable way the main topics that made that other proposal to emerge.

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Mar 19, 2018

Contributor

@mgtitimoli, @zocky, to be specific: I think any proposal which allows

class A {
  private field;
  constructor() {
    this.field = 0;
  }
}

to create an object with a public field holding 0, is unacceptably confusing. This is true no matter what the syntax for accessing the private field is, as long as it is not this.x (which it can't be for reasons also given in the FAQ).

I think this is a very important constraint, which is why it is the first question in the FAQ.

static doesn't bother me because people familiar with other languages will not be misled about which object gets the field.

Is this a technical objection, or just a matter of taste?

I don't really understand the distinction. My concern is not that I find it personally distasteful but rather that I find it misleading, if that's what you're asking.

Contributor

bakkot commented Mar 19, 2018

@mgtitimoli, @zocky, to be specific: I think any proposal which allows

class A {
  private field;
  constructor() {
    this.field = 0;
  }
}

to create an object with a public field holding 0, is unacceptably confusing. This is true no matter what the syntax for accessing the private field is, as long as it is not this.x (which it can't be for reasons also given in the FAQ).

I think this is a very important constraint, which is why it is the first question in the FAQ.

static doesn't bother me because people familiar with other languages will not be misled about which object gets the field.

Is this a technical objection, or just a matter of taste?

I don't really understand the distinction. My concern is not that I find it personally distasteful but rather that I find it misleading, if that's what you're asking.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

@bakkot I noticed you mentioned this way in your previous message, just to be all aligned, what @zocky proposed is to access private members using the following construction:

private.field = 0;

and not

this.field = 0;

mgtitimoli commented Mar 19, 2018

@bakkot I noticed you mentioned this way in your previous message, just to be all aligned, what @zocky proposed is to access private members using the following construction:

private.field = 0;

and not

this.field = 0;

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Mar 19, 2018

Contributor

@mgtitimoli, right, that is the problem. this.field = 0 will continue to be legal syntax; it will just silently create a public field rather than writing to the private field.

Contributor

bakkot commented Mar 19, 2018

@mgtitimoli, right, that is the problem. this.field = 0 will continue to be legal syntax; it will just silently create a public field rather than writing to the private field.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

@bakkot Just for you to know, they are currently evaluating using this->field to address the same as this proposal in the JS Classes 1.1 Proposal, and with this what I'm trying to say is that without having gotten too deep into the parsing rules and constraints they have defined there, the use of this->field is very similar to private.field, so this means the issue you wrote could be tackled on some similar way they are planning to tackle it there.

mgtitimoli commented Mar 19, 2018

@bakkot Just for you to know, they are currently evaluating using this->field to address the same as this proposal in the JS Classes 1.1 Proposal, and with this what I'm trying to say is that without having gotten too deep into the parsing rules and constraints they have defined there, the use of this->field is very similar to private.field, so this means the issue you wrote could be tackled on some similar way they are planning to tackle it there.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

@bakkot - So basically, your objection is that somebody could accidentally introduce a logical error in their program? I struggle to see how that's a strong objection.

Especially since the current proposal includes things like this['#x']being distinct from this.#x, which is equally if not more confusing. And AFAICT, it gives no way to access private fields with calculated names, i.e. no square brackets, thus breaking the existing conventions and introducing new asymmetries.

zocky commented Mar 19, 2018

@bakkot - So basically, your objection is that somebody could accidentally introduce a logical error in their program? I struggle to see how that's a strong objection.

Especially since the current proposal includes things like this['#x']being distinct from this.#x, which is equally if not more confusing. And AFAICT, it gives no way to access private fields with calculated names, i.e. no square brackets, thus breaking the existing conventions and introducing new asymmetries.

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Mar 19, 2018

Contributor

@mgtitimoli

That proposal notably does not use private x to declare fields.

@zocky

I think "this feature does not make it significantly more likely programmers will introduce subtle logic errors" is one of the single most important constraints of language design. I know not everyone shares this opinion, but I think it's something I and the rest of TC39 are mostly pretty set on, and you are not super likely to persuade us to give it up.

Contributor

bakkot commented Mar 19, 2018

@mgtitimoli

That proposal notably does not use private x to declare fields.

@zocky

I think "this feature does not make it significantly more likely programmers will introduce subtle logic errors" is one of the single most important constraints of language design. I know not everyone shares this opinion, but I think it's something I and the rest of TC39 are mostly pretty set on, and you are not super likely to persuade us to give it up.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

@bakkot - All I'm saying is that the assumption that it's significantly likely to introduce errors doesn't seem to be backed with anything except an assertion in the FAQ.

In any case, it's a matter of pros and cons. This proposal allows all the functionality that's covered by the other syntax and does it in a clearly defined and unambiguous manner.

In addition,

  • it allows square brackets to be used for the private members in exactly the same way as has been done in javascript since the beginning
  • doesn't introduce any new characters in the syntax, which, based on the threads in this repo, seems to be widely disliked
  • allows the source code to be read out loud (or in thought), avoiding the naming issue which has been brought up

The one con seems to be the possibility that programmers coming from other languages might misunderstand it.

zocky commented Mar 19, 2018

@bakkot - All I'm saying is that the assumption that it's significantly likely to introduce errors doesn't seem to be backed with anything except an assertion in the FAQ.

In any case, it's a matter of pros and cons. This proposal allows all the functionality that's covered by the other syntax and does it in a clearly defined and unambiguous manner.

In addition,

  • it allows square brackets to be used for the private members in exactly the same way as has been done in javascript since the beginning
  • doesn't introduce any new characters in the syntax, which, based on the threads in this repo, seems to be widely disliked
  • allows the source code to be read out loud (or in thought), avoiding the naming issue which has been brought up

The one con seems to be the possibility that programmers coming from other languages might misunderstand it.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

And for the record, I would personally prefer private x for declaration and this.x for access, because that's how it works in every other language, and is the only way that allows fields to be simply switched between being public and private without having to edit every method that uses them. But that option is explicitly disallowed in the FAQ.

zocky commented Mar 19, 2018

And for the record, I would personally prefer private x for declaration and this.x for access, because that's how it works in every other language, and is the only way that allows fields to be simply switched between being public and private without having to edit every method that uses them. But that option is explicitly disallowed in the FAQ.

@thysultan

This comment has been minimized.

Show comment
Hide comment
@thysultan

thysultan Mar 19, 2018

Another point that might be brought up against the use of a private keyword which https://github.com/zenparsing/js-classes-1.1/ makes an effort to avoid is with TypeScript's differing semantics.

That said the simplicity of this alternative looks both easy to learn, teach, and most of all look at, – and given it has been a reserved keyword in strict mode for a while i just hope that this is not held back solely by the precedence of languages that have differing heuristics with regards to the private keyword.

thysultan commented Mar 19, 2018

Another point that might be brought up against the use of a private keyword which https://github.com/zenparsing/js-classes-1.1/ makes an effort to avoid is with TypeScript's differing semantics.

That said the simplicity of this alternative looks both easy to learn, teach, and most of all look at, – and given it has been a reserved keyword in strict mode for a while i just hope that this is not held back solely by the precedence of languages that have differing heuristics with regards to the private keyword.

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

The main point IMHO this other way introduces is the alternative to use something different from this in the receiver part of the call, and this hasn't been discussed before.

Ok, there are issues with private, that's fine, let's use self.field (or another keyword), but let's give us a chance to focus on what keyword to use before the dot, and not change the dot for .# or even worse, for ->

mgtitimoli commented Mar 19, 2018

The main point IMHO this other way introduces is the alternative to use something different from this in the receiver part of the call, and this hasn't been discussed before.

Ok, there are issues with private, that's fine, let's use self.field (or another keyword), but let's give us a chance to focus on what keyword to use before the dot, and not change the dot for .# or even worse, for ->

@mgtitimoli

This comment has been minimized.

Show comment
Hide comment
@mgtitimoli

mgtitimoli Mar 19, 2018

And the other advantage is that with just one keyword we could do both: define what's private and access it.

mgtitimoli commented Mar 19, 2018

And the other advantage is that with just one keyword we could do both: define what's private and access it.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

Food for thought, with plenty of caveats:

Yet another possibility would be to use var instead of private, with the same semantics. It would have the advantage of making it natural to use bare field names, since they are declared the same as variables.

class A {
  var field;
  constructor() {
    field = 0;
  }
  equals (other) {
    return field == var(other).field
  }
}

To provide expected behaviour in other contexts, this could work also for other variables that are theoretically in lexical scope, but are hidden:

function foo() {
  var a = 1;
  
  function bar() {
     var a = 2;
     console.log(a, var(foo).a) // outputs 2,1
  }
}

I fully realize that this is impractical and possibly inconsistent, because it's hard to polyfill and because the function name can be hidden as well, and because the function name doesn't really refer to the closure but rather to the function itself. But it might help us think.

zocky commented Mar 19, 2018

Food for thought, with plenty of caveats:

Yet another possibility would be to use var instead of private, with the same semantics. It would have the advantage of making it natural to use bare field names, since they are declared the same as variables.

class A {
  var field;
  constructor() {
    field = 0;
  }
  equals (other) {
    return field == var(other).field
  }
}

To provide expected behaviour in other contexts, this could work also for other variables that are theoretically in lexical scope, but are hidden:

function foo() {
  var a = 1;
  
  function bar() {
     var a = 2;
     console.log(a, var(foo).a) // outputs 2,1
  }
}

I fully realize that this is impractical and possibly inconsistent, because it's hard to polyfill and because the function name can be hidden as well, and because the function name doesn't really refer to the closure but rather to the function itself. But it might help us think.

@hax

This comment has been minimized.

Show comment
Hide comment
@hax

hax Mar 19, 2018

@zocky var(foo).a in your last comment seems odd because . means property lookup. But a is not a property. I would like use var(foo)::a which use :: as a scope operator. But if we introduce an operator here, it could be simply written as foo::a. This just bring me to class1.1 proposal, you just need to change -> to ::.

hax commented Mar 19, 2018

@zocky var(foo).a in your last comment seems odd because . means property lookup. But a is not a property. I would like use var(foo)::a which use :: as a scope operator. But if we introduce an operator here, it could be simply written as foo::a. This just bring me to class1.1 proposal, you just need to change -> to ::.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Mar 19, 2018

Let's not get sidetracked with this var thing, it's really a separate matter.

zocky commented Mar 19, 2018

Let's not get sidetracked with this var thing, it's really a separate matter.

@thysultan

This comment has been minimized.

Show comment
Hide comment
@thysultan

thysultan Mar 28, 2018

What would it take to present this alternative as a formal proposal?

thysultan commented Mar 28, 2018

What would it take to present this alternative as a formal proposal?

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Apr 1, 2018

Member

@thysultan You'd have to convince a TC39 delegate to champion it. Given the renewed consensus in favor of # in the March 2018 meeting, and the arguments that @bakkot presented above, I think this will be difficult.

Member

littledan commented Apr 1, 2018

@thysultan You'd have to convince a TC39 delegate to champion it. Given the renewed consensus in favor of # in the March 2018 meeting, and the arguments that @bakkot presented above, I think this will be difficult.

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 9, 2018

@mmis1000 Please find the repl.it based on your example illuminating exactly what I meant in issuecomment-403544625: https://repl.it/@bdistin/ClassInClassPrivateAccess

Edit: In short, there is nothing you can do in class B to get the private.a field from class A. (even if class B is declared in class A) As it should be (encapsulation is important after all). Therefore, there could never be a time when private(this).prop !== private.prop.

bdistin commented Jul 9, 2018

@mmis1000 Please find the repl.it based on your example illuminating exactly what I meant in issuecomment-403544625: https://repl.it/@bdistin/ClassInClassPrivateAccess

Edit: In short, there is nothing you can do in class B to get the private.a field from class A. (even if class B is declared in class A) As it should be (encapsulation is important after all). Therefore, there could never be a time when private(this).prop !== private.prop.

@mmis1000

This comment has been minimized.

Show comment
Hide comment
@mmis1000

mmis1000 Jul 9, 2018

That's because you wrote the wrong the type check there. Just fix the type assert there, you will get the wtf result. why are you checking you self is type of B and access property of A?
https://repl.it/@mmis10002/ClassInClassPrivateAccess.
If you use typescript to write this, it should tell you, B class didn't have that field

mmis1000 commented Jul 9, 2018

That's because you wrote the wrong the type check there. Just fix the type assert there, you will get the wtf result. why are you checking you self is type of B and access property of A?
https://repl.it/@mmis10002/ClassInClassPrivateAccess.
If you use typescript to write this, it should tell you, B class didn't have that field

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 10, 2018

I stand corrected. I have been trying all sorts of iterations in chrome (with harmony enabled ofc) and I have come up with some very unexpected results in V8's implementation of private fields: img

Now I can't say if that's a bug in V8 or even an oversight of the specification, but those results break my interpretation of "hard-encapsulation".

  1. You should not be able to access the private members of a different class (like on left) unless the class chooses to reveal them. I don't think a class contained in another class counts as the containing class explicitly choosing to reveal it's private members.
  2. Assuming a class being inside of another class counts as explicitly choosing to reveal it's private members. Assigning its own private member with the same name should not affect the value or visibility of the containing classes private member with the this of the containing class instance. (like on the right)

I wonder if this is expected behavior from any tc39 members?

bdistin commented Jul 10, 2018

I stand corrected. I have been trying all sorts of iterations in chrome (with harmony enabled ofc) and I have come up with some very unexpected results in V8's implementation of private fields: img

Now I can't say if that's a bug in V8 or even an oversight of the specification, but those results break my interpretation of "hard-encapsulation".

  1. You should not be able to access the private members of a different class (like on left) unless the class chooses to reveal them. I don't think a class contained in another class counts as the containing class explicitly choosing to reveal it's private members.
  2. Assuming a class being inside of another class counts as explicitly choosing to reveal it's private members. Assigning its own private member with the same name should not affect the value or visibility of the containing classes private member with the this of the containing class instance. (like on the right)

I wonder if this is expected behavior from any tc39 members?

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Jul 10, 2018

Contributor

A nested class is part of outer class's code; I would be surprised if it were forbidden to access the outer class's private fields. But if it shadows those field by declaring its own, then referring to that name should be an attempt to access its own private field rather than the private field of the outer class.

By analogy:

function A {
  let a = 'A';
  function B() {
    console.log(a);
  }
  B(); // prints 'a'
}
function A {
  let a = 'A';
  function B() {
    let a = 'B';
    console.log(a);
  }
  B(); // prints 'b'
}

This is exactly the behavior I expect, yes.

Contributor

bakkot commented Jul 10, 2018

A nested class is part of outer class's code; I would be surprised if it were forbidden to access the outer class's private fields. But if it shadows those field by declaring its own, then referring to that name should be an attempt to access its own private field rather than the private field of the outer class.

By analogy:

function A {
  let a = 'A';
  function B() {
    console.log(a);
  }
  B(); // prints 'a'
}
function A {
  let a = 'A';
  function B() {
    let a = 'B';
    console.log(a);
  }
  B(); // prints 'b'
}

This is exactly the behavior I expect, yes.

@mmis1000

This comment has been minimized.

Show comment
Hide comment
@mmis1000

mmis1000 Jul 10, 2018

@bdistin
nested function/class is a natural of js, forbid this makes no sense at all, you are in js not java, there is nothing bound by some class or instance or not, there is only called on some class or instance.
this in js is a read-only local variable that automatically assigned with the object that owns the called method (undefined if the method is not called on anything), not class instance.
Private here is also not private by field, but it is instead private by the property name or so called internal slot.
nested class can access parent class's private field makes sense as it is declared inside the parent class, so there is no one except you can declare it.
You can't use the private field name outside the class definition, so no one is going to break your security things.
I thought you just try to understand how js works in Java way, but it's really not how it works.
JavaScript compare to java is just like hotdog compare to dog, and it is also never trying to become another copy of java.

mmis1000 commented Jul 10, 2018

@bdistin
nested function/class is a natural of js, forbid this makes no sense at all, you are in js not java, there is nothing bound by some class or instance or not, there is only called on some class or instance.
this in js is a read-only local variable that automatically assigned with the object that owns the called method (undefined if the method is not called on anything), not class instance.
Private here is also not private by field, but it is instead private by the property name or so called internal slot.
nested class can access parent class's private field makes sense as it is declared inside the parent class, so there is no one except you can declare it.
You can't use the private field name outside the class definition, so no one is going to break your security things.
I thought you just try to understand how js works in Java way, but it's really not how it works.
JavaScript compare to java is just like hotdog compare to dog, and it is also never trying to become another copy of java.

@maple3142

This comment has been minimized.

Show comment
Hide comment
@maple3142

maple3142 Jul 10, 2018

Is this a valid syntax?

class A {
  private a=1
}
const a=new A()
class B{
  m1(){
    return private.a
  }
  static m2(){
    return private.a
  }
}
new B().m1.apply(a) //1?
B.m2.apply(a) //1?
private(a).a //1?

maple3142 commented Jul 10, 2018

Is this a valid syntax?

class A {
  private a=1
}
const a=new A()
class B{
  m1(){
    return private.a
  }
  static m2(){
    return private.a
  }
}
new B().m1.apply(a) //1?
B.m2.apply(a) //1?
private(a).a //1?
@mmis1000

This comment has been minimized.

Show comment
Hide comment
@mmis1000

mmis1000 Jul 10, 2018

@maple3142
obviously No.
Private field can only be access in the class {...} quote, that's what the only purpose it exist.
Even two class has a private field with same name, they are not the same one
And in your example, B does never declare any private field with name a, so it is a syntax or runtime error.

mmis1000 commented Jul 10, 2018

@maple3142
obviously No.
Private field can only be access in the class {...} quote, that's what the only purpose it exist.
Even two class has a private field with same name, they are not the same one
And in your example, B does never declare any private field with name a, so it is a syntax or runtime error.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Jul 10, 2018

@maple3142

private is lexically bound, just like variables in closures. It will never point to anything else than to the collection of private fields of the object that was created with the class definition. And in any case, you cannot use private(a) outside the class definition of whatever class a belongs to.

All of this is a bit hazy and somewhat conceptually incompatible with a prototype-oriented language like javascript. But that this is equally problematic with any approach to private fields in javascript, including the variation with '#' sigils.

Despite proposing this particular syntactic solution myself, I'm leaning towards the conclusion that the idea of having a rock-solid data protection of private fields with javascript classes is conceptually wrong. There's too much prototype, apply, etc. baggage in javascript "class-like" logic. People who want that kind of thing should probably start a new language that compiles directly to WASM and doesn't allow prototype walking or applying functions to random objects at all.

zocky commented Jul 10, 2018

@maple3142

private is lexically bound, just like variables in closures. It will never point to anything else than to the collection of private fields of the object that was created with the class definition. And in any case, you cannot use private(a) outside the class definition of whatever class a belongs to.

All of this is a bit hazy and somewhat conceptually incompatible with a prototype-oriented language like javascript. But that this is equally problematic with any approach to private fields in javascript, including the variation with '#' sigils.

Despite proposing this particular syntactic solution myself, I'm leaning towards the conclusion that the idea of having a rock-solid data protection of private fields with javascript classes is conceptually wrong. There's too much prototype, apply, etc. baggage in javascript "class-like" logic. People who want that kind of thing should probably start a new language that compiles directly to WASM and doesn't allow prototype walking or applying functions to random objects at all.

@maple3142

This comment has been minimized.

Show comment
Hide comment
@maple3142

maple3142 Jul 10, 2018

But according to @bdistin 's v8 screenshot, isn't private related to this context?

@mmis1000 What if class B has private a, but still use new B().m1.apply(new A())?

maple3142 commented Jul 10, 2018

But according to @bdistin 's v8 screenshot, isn't private related to this context?

@mmis1000 What if class B has private a, but still use new B().m1.apply(new A())?

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Jul 10, 2018

Member

The disagreement about what the scoping should be in this thread (with multiple interpretations that are considered clearly correct) seems to point to the difficulty of using private.x rather than this.#x.

Member

littledan commented Jul 10, 2018

The disagreement about what the scoping should be in this thread (with multiple interpretations that are considered clearly correct) seems to point to the difficulty of using private.x rather than this.#x.

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 10, 2018

I disagree @littledan , it's just as confusing an unexpected with this.#x for a 20 year Javascript user who has never touched java (btw @mmis1000).

And that example is misleading @bakkot as the following is true: img

If changing the this of the called function is ok to get the private field of a containing class, you should be able to get that field even if the nested class declares it's own version of that field; just like we can do above with public fields.

edit: I will go a step further. If it weren't for discussing private in human terms, rather than # Sigil, we wouldn't have identified what I can only describe as a spec failure of private. Logically and objectively only the left or right should be correct on the screenshot I posted last night.

While I still believe that the left is a breach of hard-encapsulation (because you are getting private members of a class that isn't itself) and since hard-encapsulation is the main goal of private in javascript the left should be incorrect, I recognize that's my opinion. I am open to the idea that the left could be the correct result, but if the left is correct, that means the right cannot be.

bdistin commented Jul 10, 2018

I disagree @littledan , it's just as confusing an unexpected with this.#x for a 20 year Javascript user who has never touched java (btw @mmis1000).

And that example is misleading @bakkot as the following is true: img

If changing the this of the called function is ok to get the private field of a containing class, you should be able to get that field even if the nested class declares it's own version of that field; just like we can do above with public fields.

edit: I will go a step further. If it weren't for discussing private in human terms, rather than # Sigil, we wouldn't have identified what I can only describe as a spec failure of private. Logically and objectively only the left or right should be correct on the screenshot I posted last night.

While I still believe that the left is a breach of hard-encapsulation (because you are getting private members of a class that isn't itself) and since hard-encapsulation is the main goal of private in javascript the left should be incorrect, I recognize that's my opinion. I am open to the idea that the left could be the correct result, but if the left is correct, that means the right cannot be.

@mmis1000

This comment has been minimized.

Show comment
Hide comment
@mmis1000

mmis1000 Jul 10, 2018

@bdistin if you wish for that to happen, you will need to tell the compiler which of #a you referred to, because the #a of each classed are obviously different things, You can't ask runtime to guess that.
Unlike java, js does not provide a way to tell the compiler what did this value to use as before it run.

class A {
  #a = '1';
  
  init() {
    class B extends class A {
      #a = '2';

      log () {
        console.log(this.#a)
      }
    }

    new B().log()
  }
}

new A().init()

If you wish the inner class can access outer class's private property regardless of whether it is shaded. how should we tell the engine to do in this case?

If you are in some strong typed language, you just can just case this to A, and the engine will realize it should access which field, but in js, we don't do that.

It isn't impossible to do that, but the current syntax(both #a and private.a) does not have enough information for the engine to do it correctly(TypeScript may do, but we are not talking about them. (they not only reject to do that to access outer class's private property prevent confusing, they also error out if the inner and outer class have the same private field name.)
Make a same field name in a method to have different meaning is just too crazy.

mmis1000 commented Jul 10, 2018

@bdistin if you wish for that to happen, you will need to tell the compiler which of #a you referred to, because the #a of each classed are obviously different things, You can't ask runtime to guess that.
Unlike java, js does not provide a way to tell the compiler what did this value to use as before it run.

class A {
  #a = '1';
  
  init() {
    class B extends class A {
      #a = '2';

      log () {
        console.log(this.#a)
      }
    }

    new B().log()
  }
}

new A().init()

If you wish the inner class can access outer class's private property regardless of whether it is shaded. how should we tell the engine to do in this case?

If you are in some strong typed language, you just can just case this to A, and the engine will realize it should access which field, but in js, we don't do that.

It isn't impossible to do that, but the current syntax(both #a and private.a) does not have enough information for the engine to do it correctly(TypeScript may do, but we are not talking about them. (they not only reject to do that to access outer class's private property prevent confusing, they also error out if the inner and outer class have the same private field name.)
Make a same field name in a method to have different meaning is just too crazy.

@littledan littledan closed this Jul 10, 2018

@littledan littledan reopened this Jul 10, 2018

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Jul 10, 2018

Member

Sorry clicked close by accident.

Member

littledan commented Jul 10, 2018

Sorry clicked close by accident.

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 10, 2018

@mmis1000 There is no ambiguity in your example, the console logged will be '2'. But if you call new B().log.call(this) the console should log '1', not an error, just as if you didn't declare the #a in class B.

edit: That is assuming accessing containing class private is correct.

bdistin commented Jul 10, 2018

@mmis1000 There is no ambiguity in your example, the console logged will be '2'. But if you call new B().log.call(this) the console should log '1', not an error, just as if you didn't declare the #a in class B.

edit: That is assuming accessing containing class private is correct.

@jridgewell

This comment has been minimized.

Show comment
Hide comment
@jridgewell

jridgewell Jul 10, 2018

Member

But if you call new B().log.call(this) the console should log '1', not an error,

Incorrect, this will throw. Declaring #a inside the inner class has changed the meaning of this.#a, it no longer refers to the outer #a declaration.

Member

jridgewell commented Jul 10, 2018

But if you call new B().log.call(this) the console should log '1', not an error,

Incorrect, this will throw. Declaring #a inside the inner class has changed the meaning of this.#a, it no longer refers to the outer #a declaration.

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 10, 2018

the inner class has changed the meaning of this.#a

If you are going with this syntax, then #a should always be relative to the this; should it not?

bdistin commented Jul 10, 2018

the inner class has changed the meaning of this.#a

If you are going with this syntax, then #a should always be relative to the this; should it not?

@mmis1000

This comment has been minimized.

Show comment
Hide comment
@mmis1000

mmis1000 Jul 10, 2018

@bdistin I though, the way of symbol lookup is a different issue than this issue for.
And no, this issue does not change how this proposal works internaly, it is just an alternative syntax.
You may be even possible to implement your own babel plugin to transform the syntax this issue proposed to the sucking sigil syntax.
Isn't it better to open another issue to discuss about the symbol lookup problem?
Although I am distrust about the TC39 Team, I don't even think them will hear about this.
They already ignored 90% of the developer's opinion to choose a syntax no one wishes.
And they are likely to continuing do that.

mmis1000 commented Jul 10, 2018

@bdistin I though, the way of symbol lookup is a different issue than this issue for.
And no, this issue does not change how this proposal works internaly, it is just an alternative syntax.
You may be even possible to implement your own babel plugin to transform the syntax this issue proposed to the sucking sigil syntax.
Isn't it better to open another issue to discuss about the symbol lookup problem?
Although I am distrust about the TC39 Team, I don't even think them will hear about this.
They already ignored 90% of the developer's opinion to choose a syntax no one wishes.
And they are likely to continuing do that.

@bdistin

This comment has been minimized.

Show comment
Hide comment
@bdistin

bdistin Jul 10, 2018

I apologize to the OP for going so off topic. I have created a new issue for the separate issue with Javascript's specification of private fields.

bdistin commented Jul 10, 2018

I apologize to the OP for going so off topic. I have created a new issue for the separate issue with Javascript's specification of private fields.

@hax

This comment has been minimized.

Show comment
Hide comment
@hax

hax Jul 12, 2018

I think @bdistin 's idea is still related to this (private.x vs this.#x) issue.

The confusion is coming from the this.x semantic, aka, property lookup semantic --- which always look up indistinguishable string x in the dynamic scoped this.

But #x is statically resolved, and it's not a property(string) at all, so this.#x (the hybridization of this.x and #x) is potentially confused.

This is why proposal like classes 1.1 suggest use a totally different operator for private state access (this->x or this::x) which I believe is the best way to avoid such confusion.

But even we still use ., I feel private.x is better than this.#x, because I guess most programmers see private.x as a whole -- private is a keyword, and there is no individual private object/reference like this. So private.x will be easily understand as a meta property like function.new which never imply property lookup semantic.

I feel it's much easy to teach and learn private.x is resolved statically, compare to teach and learn a new rule of already complex this semantic --- it seems many tc39 members believe this is too complex and refuse to add any even useful feature related to this. Though I don't agree your attitude to this, but at least please don't apply double standards in private proposals.

hax commented Jul 12, 2018

I think @bdistin 's idea is still related to this (private.x vs this.#x) issue.

The confusion is coming from the this.x semantic, aka, property lookup semantic --- which always look up indistinguishable string x in the dynamic scoped this.

But #x is statically resolved, and it's not a property(string) at all, so this.#x (the hybridization of this.x and #x) is potentially confused.

This is why proposal like classes 1.1 suggest use a totally different operator for private state access (this->x or this::x) which I believe is the best way to avoid such confusion.

But even we still use ., I feel private.x is better than this.#x, because I guess most programmers see private.x as a whole -- private is a keyword, and there is no individual private object/reference like this. So private.x will be easily understand as a meta property like function.new which never imply property lookup semantic.

I feel it's much easy to teach and learn private.x is resolved statically, compare to teach and learn a new rule of already complex this semantic --- it seems many tc39 members believe this is too complex and refuse to add any even useful feature related to this. Though I don't agree your attitude to this, but at least please don't apply double standards in private proposals.

@zocky

This comment has been minimized.

Show comment
Hide comment
@zocky

zocky Jul 13, 2018

@littledan

The disagreement about what the scoping should be in this thread (with multiple interpretations that are considered clearly correct) seems to point to the difficulty of using private.x rather than this.#x.

Actually, this can be very easily clarified, by stating that private === private(this), and then applying the exact same rules for allowing access to private.foo as you would for this.#foo in the sigil version.

zocky commented Jul 13, 2018

@littledan

The disagreement about what the scoping should be in this thread (with multiple interpretations that are considered clearly correct) seems to point to the difficulty of using private.x rather than this.#x.

Actually, this can be very easily clarified, by stating that private === private(this), and then applying the exact same rules for allowing access to private.foo as you would for this.#foo in the sigil version.

@simonbuerger

This comment has been minimized.

Show comment
Hide comment
@simonbuerger

simonbuerger Oct 6, 2018

Best argument against using the # has to be the precedent if you wanted to add more access modifiers in future. What if we want both private and protected class methods. Or internal (I'm borrowing from C# access modifiers here) Or allow multiple access modifiers "private protected" or "protected internal"? To go with the current proposal will you have this.#£a or this.~a or this.~#a? Just typing that out now made me shudder 😨

If not then how would we explain to new JS developers that some access modifiers are keywords (static, maybe others in future) and some are this new strange prefix syntax?

simonbuerger commented Oct 6, 2018

Best argument against using the # has to be the precedent if you wanted to add more access modifiers in future. What if we want both private and protected class methods. Or internal (I'm borrowing from C# access modifiers here) Or allow multiple access modifiers "private protected" or "protected internal"? To go with the current proposal will you have this.#£a or this.~a or this.~#a? Just typing that out now made me shudder 😨

If not then how would we explain to new JS developers that some access modifiers are keywords (static, maybe others in future) and some are this new strange prefix syntax?

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Oct 11, 2018

Member

Thanks for all of your comments on this thread. In the end, TC39 is moving ahead with the proposal described in this repository, which remains at Stage 3 after a long year of reconsidering various alternatives. See the README for more details.

Member

littledan commented Oct 11, 2018

Thanks for all of your comments on this thread. In the end, TC39 is moving ahead with the proposal described in this repository, which remains at Stage 3 after a long year of reconsidering various alternatives. See the README for more details.

@littledan littledan closed this Oct 11, 2018

@simonbuerger

This comment has been minimized.

Show comment
Hide comment
@simonbuerger

simonbuerger Oct 11, 2018

Really appreciate the work done by tc39, but it ultimately feels like you're saying that you've taken a year to pretty much disregard all the strong opposition and alternatives put forward to the syntax of this proposal from the development community.

Saying you considered them but ultimately decided your way was the best doesn't feel like enough of a technical explanation to me. Is there anywhere we can see more of the additional discussion (other than here)?

In a language that has heavily favoured semantically clear keywords in the past - import, export, class, static, break, continue, etc. etc. this seems inconsistent.

simonbuerger commented Oct 11, 2018

Really appreciate the work done by tc39, but it ultimately feels like you're saying that you've taken a year to pretty much disregard all the strong opposition and alternatives put forward to the syntax of this proposal from the development community.

Saying you considered them but ultimately decided your way was the best doesn't feel like enough of a technical explanation to me. Is there anywhere we can see more of the additional discussion (other than here)?

In a language that has heavily favoured semantically clear keywords in the past - import, export, class, static, break, continue, etc. etc. this seems inconsistent.

@bakkot

This comment has been minimized.

Show comment
Hide comment
@bakkot

bakkot Oct 11, 2018

Contributor

@simonbuerger The issues on this repo, on proposal-class-fields, and on proposal-private-fields contain most of the discussion (check closed issues especially). The private syntax FAQ gives a high-level summary of a lot of our thinking, and tc39/proposal-private-fields#14 in particular is a long thread discussing alternatives and their feasibility or lack thereof in great detail.

Contributor

bakkot commented Oct 11, 2018

@simonbuerger The issues on this repo, on proposal-class-fields, and on proposal-private-fields contain most of the discussion (check closed issues especially). The private syntax FAQ gives a high-level summary of a lot of our thinking, and tc39/proposal-private-fields#14 in particular is a long thread discussing alternatives and their feasibility or lack thereof in great detail.

@simonbuerger

This comment has been minimized.

Show comment
Hide comment
@simonbuerger

simonbuerger Oct 11, 2018

@bakkot thank you for taking the time to share that with me for the context. I can see the thorough debate that went on. I guess it may take me a good long while to get past the general "ickyness" of the whole this.#privateMethod thing ;)

simonbuerger commented Oct 11, 2018

@bakkot thank you for taking the time to share that with me for the context. I can see the thorough debate that went on. I guess it may take me a good long while to get past the general "ickyness" of the whole this.#privateMethod thing ;)

@simonbuerger

This comment has been minimized.

Show comment
Hide comment
@simonbuerger

simonbuerger Oct 11, 2018

As someone coming from a strong html and CSS background, semantics of a language/language feature is the most important consideration, and quite frankly # carries no semantic meaning whatsoever. Ok, I'm done.

simonbuerger commented Oct 11, 2018

As someone coming from a strong html and CSS background, semantics of a language/language feature is the most important consideration, and quite frankly # carries no semantic meaning whatsoever. Ok, I'm done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment