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

Symbol.private syntax and proposal resolution #206

Closed
Igmat opened this issue Jan 8, 2019 · 11 comments
Closed

Symbol.private syntax and proposal resolution #206

Igmat opened this issue Jan 8, 2019 · 11 comments

Comments

@Igmat
Copy link

Igmat commented Jan 8, 2019

I see Symbol.private as viable alternative to the way privates presented in existing proposal. Most of you probably knows such position of mine.

Most of the time Symbol.private was a rejected because of:

  1. Issue with Membrane pattern
  2. Lack of branding
  3. Doesn't work the same way as [[InternalSlot]]
  4. Lack of syntax

1 - is solved in #183
2 - could be easily added by one simple decorator, when really needed, in case of Symbol.private. While being implictly added to highly required on its own encapsulation feature (as in existing proposal) leads to unsovable problems like #106, especially for metaprogramming libraries'/frameworks' authors (like me, Vue.js, Aurellia, etc.)
3 - while its questionable why should encapsulated state work in same way as internal slots at all, if it really needed, then solution for this question is easy - lets use Symbol.private instead of [[InternalSlot]]
4 - I'll provide solution for this in this particular thread (I hope it will be final one, since I already provided quite a few)

Unless I misunderstood something in @littledan's list, all major concernes are related to this 4 points. I answered each and every point from @littledan's list, because it is the most complete answer to Symbol.private (few points were unclear to me, so I'm waiting for continuing that discussion). You may join our discussion and see my points here.

Just for history

Lists of already suggested shorthand sytnaxes for Symbol.private.
Class only:

  1. [Private] yet another alternative to #-based syntax #149
  2. [Private] Metaprogramming vs Security #134
  3. [Private] yet another alternative to #-based syntax #149 (comment)
  4. @rdking's class-members

For all symbols:

  1. @zenparsing's Symbol literals
  2. This particular thread will show something very close to Symbol literals, but I got this idea independently and it has few differences

Syntax

Declaration

Use public, private (protected as follow-up) as declaration keyword, like var/let/const.

let x; // variable declaration
public #x; // create `Symbol()` and store it in lexically scoped constant `#x`
private #y; // create `Symbol.private()` and store it in lexically scoped constant `#y`

Assigment

Any assignment without receiver leads to early SyntaxError

public #x = 1; // SyntaxError
public #y = Symbol(); // SyntaxError
public [#z] = [Symbol()]; // SyntaxError
#k = 1; // SyntaxError
#l = Symbol(); // SyntaxError
[#m] = Symbol(); // SyntaxError

Proper assignment always has receiver, [] and has no keyword.

public #x;
obj[#x] = 1;

Declaration + computed property syntax in object literals

The true power comes with computed property syntax.

public #x;
existingObj[#y] = 1; // throws because #y isn't declared yet
const obj = {
    [#x]: 1,
    public [#y]: 1,
    private [#z]: 1,
};
const otherObj = {
    private [#x]: 1, // shadows `#x` from outer lexical scope
    [#y]: 1, //  throws because #y isn't declared yet in this scope
    private [#z]: 1,
};

There is another idea to closure scope instead of lexical one - in this case private [#x] in otherObj will throw, since redeclaration of #x is restricted. Both ways make some use-cases easier and others - more complex, but provide same feature set, so it's a discussible question which one to prefer.

Declaration + computed property syntax in classes

Work mostly the same way as for objects.

public #x;
existingObj[#y] = 1; // throws because #y isn't declared yet
class SomeClass {
    [#x] = 1;
    public [#y] = 1;
    private [#z] = 1;
};
class SomeOtherClass = {
    private [#x] = 1; // shadowed `#x` not shared with `SomeClass`
    public [#y] = 1; // another `#y` not shared with `SomeClass`
    private [#z] = 1; // another `#z` not shared with `SomeClass`
};

Simple mental model

# stands for Symbol. Any variable starting with this sign is ALWAYS Symbol. So code like this private #x should be read as private symbol x.

Discussible moments

I used already reserved keywords, since we are safe to use them + they are good fit for such mental model. But, obviously, we could select some others, for example:

let x; // variable declaration
sym #x; // create `Symbol()` and store it in closure scoped constant `#x`
psym #y; // create `Symbol.private()` and store it in closure scoped constant `#y`
// or even
#sym #y;  // create `Symbol.private()` and store it in closure scoped constant `#y`

Possible follow-up proposals

  1. Symbol.protected/Symbol.friend/Symbol.<whatever> and <whatever> #x declaration syntax;
  2. <whatever> #x for 'key' as shorthand for const x = Symbol.for('key');
  3. Probably some others, not discovered yet.

Conclusion

It seems to cover most important ergonomic cases for private at classes. If you see any other important but still not covered cases or have any questions to this syntax feel free to ask/advice/discuss them here - I'm open to futher adjustments and believe that it could be improved in a way that will work for committee (if it's not there yet).

Proposal for futher steps

  1. Move Symbol.private to stage 1
  2. Remove private part of existing class-fields proposal, but leave it in stage 3 and normally proceed it to stage 4 whenever committee decides it's appropriate time
  3. Proceed with shorthand syntax for Symbols (this one, or @zenparsing's, or any other I mentioned before)
    • as a follow-up proposal for symbol-private-proposal
    • as a part of symbol-private-proposal
      I'm ready to create all needed documents for it (e.g. changes to spec, readme, faq, etc.) and polyfill + transpiler plugin

Also, I want to mention that our small but vocal group (me, @rdking, @shannon) has consensus on it and sees such approach as the best possible way to solve majority of existing problems. Probably @hax and @trusktr also share this position, but I'm not sure.

Does it make any sense to you (@ljhard, @littledan, @erights, @jridgewell, @zenparsing, @bakkot, @syg)?

@littledan
Copy link
Member

I'm wondering, how would people feel about discussing private symbols in @jridgewell's repository, and focusing this repository on discussing this class fields proposal?

@Igmat
Copy link
Author

Igmat commented Jan 8, 2019

First of all, that repo is a fork, so creating issues isn't allowed (or I don't know how to create them?).

And second is that two different types of privacy doesn't have much sense.

@littledan
Copy link
Member

Sounds like the lack of ability to make issues is something that should be fixed. Until then, maybe you can discuss things on @zenparsing's repository.

I agree that it'd be overkill to have multiple kinds of private. This repository is pursuing one proposal in that space, whereas that proposal is discussing another kind. We can have issues in both places, with the discussion sorted by which proposal you're working on. In TC39, the champions of each proposal can discuss the pros and cons of each, informed by the discussion in issues in their repository.

@Igmat
Copy link
Author

Igmat commented Jan 8, 2019

@littledan, I got your point. I obviously could put syntax discussion to that repo. But what is more important in this particular case is my questions regarding status quo of this class-fields proposal. And your thought about steps I proposed. I think that it makes much more sense to discuss them here, don't you agree?

@littledan
Copy link
Member

OK, to clarify, I don't plan to pursue your proposal. We had lengthy discussion about whether we should include prefixes like public in field declarations, and decided not to.

@Igmat
Copy link
Author

Igmat commented Jan 8, 2019

But it's not a field declaration, it's symbol declaration.
I proposed shorthand syntax for all kinds of Symbols, anywhere in the code. class example is here just to show how it interacts with classes. public/private keywords used to declare Symbol, not field, and my proposal has no affect on existing class-fields proposal (except I'm asking to move private part from it).

Anyway, syntax discussion could be omitted (or moved to another repo), but action items is still here (e.g. @zenparsing's Symbol literals could be used as well).

So what about answering to this part?

  • Move Symbol.private to stage 1

  • Remove private part of existing class-fields proposal, but leave it in stage 3 and normally proceed it to stage 4 whenever committee decides it's appropriate time

  • Proceed with shorthand syntax for Symbols (this one, or @zenparsing's, or any other I mentioned before)

    • as a follow-up proposal for symbol-private-proposal
    • as a part of symbol-private-proposal
      I'm ready to create all needed documents for it (e.g. changes to spec, readme, faq, etc.) and polyfill + transpiler plugin

@littledan
Copy link
Member

To answer your question, I don't plan to work towards any of those bullet points, including working on a proposal for syntax for symbols. We've discussed the reasons for this at great length in various other issues. Feel free to write a proposal and collaborate with other TC39 delegates and community members to work towards these goals.

@trusktr
Copy link

trusktr commented Jan 10, 2019

What's really,

really,

really,

really nice

about the proposal of this issue is that it works outside of classes, which makes it much more dynamic (f.e. easy to implement "friends" or other such features using it), and it can still achieve the same as what the "private" feature of class-fields proposal of this repo wants to achieve.

Plus,

  • it has good 'ol property descriptors
  • works with prototypal inheritance
  • interops with libs
     _.pick(obj, #y, #x) // gives the lib access ONLY to #y and #x
    in contrast to my idea in another thread (which isn't that bad anyways, use trusted libs),
    _.pick(obj, 'x', 'y', private) // gives the lib access to ALL privates
    We should use libs that we trust anyways.
  • Works with ES5-style classes!!!
  • etc

@Igmat There's one thing I'm missing in your examples: how does inheritance work with public [#y] = 1? How does the code in a subclass of a class in another module, or the code in a module that imports an object literal that has the public [#y] , access the properties? (I'm also curious about protected inheritance, which would probably be similar.)

Is there a syntax we could use so that names can be accessed as strings, so that it can be even more interoperable with existing libs?

@trusktr
Copy link

trusktr commented Jan 10, 2019

I don't plan to work towards any of those bullet points

@littledan It'd be awesome if you worked towards considering the pros/cons of what's offered here, rather than simply dismissing it.

@trusktr
Copy link

trusktr commented Jan 10, 2019

The growth of JavaScript is on an exponential curve it seems, and @littledan your proposal is (IMO) caught right in the cusp (new features like WASM are helping to propel JS even faster): the point prior to which community discussion was limited, and after which growing community involvement has shown that the private proposal in this repo is largely disliked by the community, but maybe not for good reasons (I explained in the other issue about future add-ons).

The right thing to do is to consider the new landscape and new possibilities (like in this issue), give them an honest chance, and delay private properties for a while longer to get the spec just right before it is embedded in stone.

Because we're in the cusp, any slow down from changes to the spec aren't going to drastically delay the release of private fields. Suppose it takes another year or two extra: that's not so bad considering the age of the discussion (at least more than a decade from my limited knowledge).

A lot of people really want this feature, and want it to be robust.

@trusktr
Copy link

trusktr commented Jan 10, 2019

Random thought: adapting from here and :private and :fooPriv from examples in #205, it could be possible that visibility helpers can be created for POJOs in such a way that allows regular . and [] access:

const priv = Symbol.private()
export const obj = {n:1}

obj:priv.foo = 1
obj:priv['pro'+'perty'] = 1

// Object.keys method is updated to take a second arg, the visibility helper
Object.keys(obj, priv) // ["foo", "property"]

const o2 = { ...obj:priv }
Object.keys(o2) // ["foo", "property"]

const o3 = { ...obj, ...obj:priv }
Object.keys(o2) // ["n", "foo", "property"]
import obj from './obj'
Object.keys(obj) // ["n"]

const priv = Symbol.private()
Object.keys(obj, priv) // []

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

3 participants