tc39 / proposal-class-fields

Orthogonally-informed combination of public and private fields proposals
https://arai-a.github.io/ecma262-compare/?pr=1668
1.72k stars 113 forks source link

[Private] yet another alternative to `#`-based syntax #149

Closed Igmat closed 5 years ago

Igmat commented 5 years ago

Disclaimer

My intent here is to show that there are good alternatives to existing private part of this proposal, that were not properly assessed. Probably it happened, because committee has a very limited time and amount of members involved, while such long time for discussion around encapsulation (years or even decades) has lead to loosing clear sight on the problem and possible solutions.

Proposal

It's based on the my proposal in #134 and Symbol.private - I won't repeat them fully here, but I hope that you may visit these links to get more background. This comment (https://github.com/tc39/proposal-class-fields/issues/147#issuecomment-430441686) to my initial proposal inspired me to make another one, which dismisses problem with this having more complex semantics than it has now.

Goals:

  1. Prevent confusion with private x / this.x pair
  2. Symmetry between this.x and obj.x
  3. Symmetry between declaration and access syntaxes

Solution

Use Symbol.private with existing lookup mechanism, existing mental model for accessing properties and small syntax sugar to make use of such symbols more ergonomic, by declaring symbol variable in lexical scope of a class.

Syntax 1 (less verbose):

class A {
    // declaring of new private symbols in lexical scope of a class
    symbol somePrivatePropertyName;
    symbol somePrivateMethodName;

    [somePrivatePropertyName] = 'this is private value';
    somePrivatePropertyName= 'this is public value with a `somePrivatePropertyName` property name';

    [somePrivateMethodName]() {
        // it is a private method
    }
    somePrivateMethodName() {
        // it is a public method with `somePrivateMethodName` name
    }
    methodA(obj) {
        this[somePrivateMethodName](); // call private method
        this.somePrivateMethodName(); // call public method
        this[somePrivatePropertyName]; // access private property
        this.somePrivatePropertyName; // access public property

        obj[somePrivateMethodName](); // call private method
        obj.somePrivateMethodName(); // call public method
        obj[somePrivatePropertyName]; // access private property
        obj.somePrivatePropertyName; // access public property
    }
}

const a = new A();
// throws `ReferenceError` since `somePrivatePropertyName` doesn't exist outside of class A
a[somePrivatePropertyName];
// access public property `somePrivatePropertyName` of `a`, as expected
a.somePrivatePropertyName;

Syntax 2 (more generic):

class A {
    // declaring of new private symbols in lexical scope of a class
    const somePrivatePropertyName = Symbol.private();
    const somePrivateMethodName = Symbol.private();

    // everything else is the same as in previous example

    // additional possibility of such syntax
    let numberOfInstanses = 0;

    constructor() {
        numberOfInstanses++;
    }
}

This option is a little bit more verbose, and probably such syntax isn't obvious enough to use for declaring private instance data, but as I shown above it could be used for something else, which is probably make some sense.

Advantages:

  1. Doesn't have any problems listed in FAQ
  2. No new semantics === easy to learn
  3. Full symmetry === prevents accidental missuses
  4. Possibility to extend in future, if needed
  5. Doesn't use #-sigil, so no risk for breaking community
  6. Compatible with TypeScript's private

Disadvantages:

  1. Not yet implemented
  2. Slogan # is the new _ couldn't be used for education

Conclusion

Since I don't pretend to replace existing proposal with this one right now, because, obviously, my proposal also needs additional assessment, preparing document that reflects changes to ES spec, creating tests, implementing babel-plugin for transpiling, adding support to JS-engines and most importantly wider discussion with community (BTW, existing also needs it), I only hope that this clearly shows the fact that there are still good alternatives, which weren't taken into account.

What is your opinion on that @littledan, @zenparsing, @jridgewell, @ljharb?

P.S.

This syntax also adds a bonus usage scenario:

class A {
    symbol marked;

    markForeignObject(foreignObject) {
        if (foreignObject[marked]) {
            // do something with knowledge that `foreignObject` was already passed to this method
        } else {
            // do something with knowledge that `foreignObject` enters here first time
            foreignObject[marked] = true;
        }
    }
}

And it's only one possible scenario, there could be dozens of them.

shannon commented 5 years ago

Yes please. And we can finally start to use symbols for what everyone I know wanted to use them for in the first place.

P.S. I prefer the first syntax as it's such a minor change to existing syntax.

hax commented 5 years ago

Syntax 2 (more generic):

class A {
    // declaring of new private symbols in lexical scope of a class
    const somePrivatePropertyName = Symbol.private();
    const somePrivateMethodName = Symbol.private();

    // everything else is the same as in previous example

    // additional possibility of such syntax
    let numberOfInstanses = 0;

    constructor() {
        numberOfInstanses++;
    }
}

Syntax 2... You use const/let in class body, they are instance vars with shorthand syntax we discussed in classes 1.1 proposal.

Copy from https://github.com/zenparsing/js-classes-1.1/issues/45#issuecomment-429985398

class Counter extends HTMLElement {
  const setX = (value) => { x = value; window.requestAnimationFrame(render); };
  const render = () => this.textContent = x.toString();
  const clicked = () => setX(x++);

  let x = 0;

  constructor() {
    super();
    this.onclick = clicked;
  }

  connectedCallback() {
    render();
  }

}
Igmat commented 5 years ago

@hax I thought it could be a confusion between this proposal and classes 1.1, even though Syntax 2 looks like classes 1.1 it has very different meaning and main difference is:

rdking commented 5 years ago

@Igmat Just how useful is the distinction?

Case 1: (Class scoped private symbol used to add instance scoped private property)

Case 2: (private data contained completely within a closure)

The kick in the shins here is that for case 1, if by some accident the private symbol is leaked, there's nothing to stop the leak of the private data. Whereas with case 2, if the developer wishes to share some of the private data, the developer must explicitly provide for that. Which is better?

Igmat commented 5 years ago

@rdking in both cases to share private data developer has to explicitly provide access, so probability of unintended leaking is equal for both solutions. There is no difference between this:

class A {
    symbol somePrivateName;
    leak() {
        return somePrivateName;
   } 
} 

and this:

class A {
    #somePrivate;
    leak() {
        return this.#somePrivate;
   } 
} 

in terms of protecting private data.

And since first allows additional usage scenarios built on that developer is able to share not only private data itself, but also private name, I think that first case is better.

shannon commented 5 years ago

@Igmat it is different because you have leaked the key to the data. Not just the data as in the second case. But I personally think this ok as it's known to be a symbol and key. People shouldn't be returning the key directly and are unlikely to do it by accident. Returning this[somePrivateName] is the equivalent.

Igmat commented 5 years ago

@shannon I didn't mean that those samples do same stuff, I meant that in terms of privacy it doesn't matter what had leaked data or key to data - in both cases encapsulation is broken.

BTW, I realized that leaking of a key seems to be even more unlikely happened than leaking of data.

rdking commented 5 years ago

@Igmat There is a small but important difference. Leaking the data only puts a copy of or reference to the data in public hands. Leaking the key, puts the private property itself in public hands. In the former case, at most, the fields in an object can be changed, but the object itself will remain in the private property. In the latter case, an object in the private property can be completely replaced. If a library keyed something using that object, being able to replace it can break code. On this point, I think my usual opponents would agree with me, being able to leak the actual name of the private field is a worse problem than just leaking the data.

shannon commented 5 years ago

@rdking yes yes, but footguns and flexibility and what not. This to me is a feature and not likely to be done by accident. Developers already understand that symbols are keys and this is a way to explicitly and simply provide access to those private properties.

Igmat commented 5 years ago

@rdking IMO it's rather feature than foot-gun, because there is mostly no chance to unintentionally reveal private symbol.

symbol somePrivate;

this is definitely declaration, more of that - it's self-explaining declaration, and the most important it doesn't interfere with any existing syntax, so it can't be misunderstood like private or #.

Because this class scoped variable has special type - Symbol, it's very unlikely that it could be confused with its value, since last one most probably has different type, so:


class A {
    symbol somePrivate;

    leak() {
        return somePrivate;
    }
    notALeak() {
        return this[somePrivate];
    }
}

There is no chance that somebody will implement something like leak(), where actually wants notALeak(), and not only because difference is obvious, but also because first usage of leak() would show that returned result can't be used in place where it was meant to use private data to which it points.


class A {
    symbol somePrivate;

    leak() {
        return { somePrivate };
    }
    notALeak() {
        return { somePrivate: this[somePrivate] };
    }
}

This won't be confused for the same reason as previous sample.


class A {
    symbol somePrivate;

    leak(cb) {
        cb(somePrivate);
    }
    notALeak() {
        cb(this[somePrivate]);
    }
}

This won't be confused also because cb function most likely expects some value and not a Symbol.


class A {
    symbol somePrivate;

    leak() {
        return { [somePrivate]: `anything could be here even private symbol itself` };
    }
}

It's even not a leak, because to access private symbol or its value outer code must have it already.


class A {
    symbol somePrivate;

    leak(obj) {
        return obj[somePrivate]= `anything could be here even private symbol itself`;
    }
}

It's not a leak, for same reason as previous sample.


I can't imagine how private symbol could leak unintentionally, so it's definitely feature, and not a bug, unless you can provide some example when it happens by an accident.

P.S.

#-private has mostly the same problem if it points to object and not a primitive.

class A {
    #somePrivate = { a: 'some private string' };

    leak() {
        return this.#somePrivate;
    }
}

After calling leak method you can mutate #somePrivate in any way you want. BTW, since # doesn't support any kind of dynamic access, using one private field (e.g. #state) and storing object with all privates there could become common practice, so last example may be even more common, than we assume now.

rdking commented 5 years ago

@shannon

yes yes, but footguns and flexibility and what not.

Funny. Notice I never claimed that being able to do so was a bad idea. Just capable of a potentially worse problem. In the end, what you're offering is a throwback to proposal-private-fields and part of the foundation of the current proposal. It eventually became the "PrivateName" concept. So while you may have some arguing against you, know it's a viable concept because it's mostly already implemented, but with uglier notation. Btw, symbol privateData just looks too much like a typed declaration. Better to choose a different word.

Igmat commented 5 years ago

@rdking, on one hand it makes sense

Btw, symbol privateData just looks too much like a typed declaration. Better to choose a different word.

on the other hand TS and flow popularized another type anotation:

let a: number;

So I don't think that symbol will be commonly treated as type instead of keyword. But, obviously, we can change it if there is better candidate. My thoughts :

  1. private - leads to #134 (pros: already reserved keyword, cons: needs additional semantic for this)
  2. name/property - aren't self-explanaing enough IMO
  3. let/const/any other already used keyword - interfere with existing usage patterns and causes confusion
  4. abstract/transient/any other reserved but unused yet keyword - aren't seem to be related enough with proposed semantics

If you have any ideas for better keyword, I would love to take a look on them :)

shannon commented 5 years ago

It might not even be necessary for the symbol to be declared in the class itself. As long as Symbol.private is implemented you can just pair it with the existing public properties proposal for it to be pretty useful. Set/Define debate aside.

const someName = Symbol.private();
class A {
   [someName] = 'private';
  ...
} 

It's easier to use than a weakmap and not incredibly verbose. This is really how I wanted symbols to work in the first place. When I learned that you could just get references to them via getOwnPropertySymbols I was very disappointed.

The symbol/whatever keyword could be added in a follow on as simple sugar. Which would make this just: drop private properties as a class construct and implement private symbols and call it a day.

shannon commented 5 years ago

However, I know people are going to say it needs to be syntax to avoid monkey patching Symbol. So there is that to consider.

Igmat commented 5 years ago

@shannon totally agree with that Symbol.private is enough by itself, but it seems that lack of simple and ergonomic syntax for declaring private properties with it is a deal-breaker for committee, so my proposals try to fulfill that gap.

littledan commented 5 years ago

The big issue with using private symbols in this sort of way is that it's unclear how to square their semantics with some integrity goals that @erights has been championing. Separately, I think that it'll be lighter-weight for people to adopt # than to switch to computed property syntax, even with the improvements in this proposal.

aimingoo commented 5 years ago

@Igmat

So, maybe you need a utils with some code typography skills only. for your case:

class A {
    // declaring of new private symbols in lexical scope of a class
    symbol somePrivatePropertyName;
    symbol somePrivateMethodName;

//    [somePrivatePropertyName] = 'this is private value';
//    somePrivatePropertyName= 'this is public value with a `somePrivatePropertyName` property name';

    [somePrivateMethodName]() {
        // it is a private method
    }
    somePrivateMethodName() {
        // it is a public method with `somePrivateMethodName` name
    }
    methodA(obj) {
        this[somePrivateMethodName](); // call private method
        this.somePrivateMethodName(); // call public method
        this[somePrivatePropertyName]; // access private property
   ...
}

^^.

There is a layout solution:

// A utils function
const definePrivated = (f, ...args) => f(...args);

// Define a class with privated names
A = definePrivated(( // define a privated name part
   somePrivatePropertyName = Symbol(),
   somePrivateMethodName = Symbol()) => class { // and a class part

   [somePrivateMethodName](x) {
      console.log("Yes, in private method:", this[somePrivatePropertyName], 'and', x[somePrivatePropertyName]);

      x[somePrivatePropertyName] = 'PrivateProperty is property and defaut is undefined value, updated.';
      x.methodB(x);
   }

   methodA() {
      this[somePrivatePropertyName] = 200;
      this[somePrivateMethodName](new A);
   }

   methodB(obj) {
      console.log(obj[somePrivatePropertyName]); // updated
   }

});

Please, try it:

> obj = new A;
> obj.methodA();
yes, in private method: 200 and undefined
PrivateProperty is property and defaut is undefined value, updated.

Done. hahaha~~

Now, No new concept! No No new semantics! No magic! No community breaking!

No new spec! Good! :+1:

shannon commented 5 years ago

@littledan

The big issue with using private symbols in this sort of way is that it's unclear how to square their semantics with some integrity goals that @erights has been championing.

Could you elaborate on the integrity goals you are referring to?

Separately, I think that it'll be lighter-weight for people to adopt # than to switch to computed property syntax, even with the improvements in this proposal.

# may be lighter weight in declaration by one character (not completely true because you have to declare the symbol first, but a computed property is only one character difference is what I meant to say). However, it's heavier in semantics, dynamic access limitations (redundant code), and I have to assume in implementation details.

@aimingoo Almost, except without Symbol.private you can access the symbols with getOwnPropretySymbols. So there would need to at least be spec for that.

Edit: for clarification

aimingoo commented 5 years ago

@Igmat @littledan

And, definePrivated with a ...args is a special design so that friendly classes can be implemented. such as:

passportcard = Symbol();

A = definePrivated(..., passportcard);
B = definePrivated(..., passportcard);

Both A and B Class can access both private members. ^^.

@littledan yes, need disable public these OwnPropertySymbols, get, enumerate, etc.

Igmat commented 5 years ago

@aimingoo friend classes are achievable in my proposal too, since private symbol could be created outside of class and shared between several class declarations. My proposal is only trying to introduce more ergonomic syntax for symbols.

@littledan are you talking about Realms proposal? If yes, what problems could be caused with it and Symbol.private implemented together?

I think that it'll be lighter-weight for people to adopt # than to switch to computed property syntax, even with the improvements in this proposal.

I'm not sure about this assumption. I have opposite opinion, especially taking into account how many issues was caused by # in community and how widely symbols and computed properties already adopted.

jridgewell commented 5 years ago

The big issue with using private symbols in this sort of way is that it's unclear how to square their semantics with some integrity goals that @erights has been championing.

All of MM's concerns can be solved with Private Symbols. His biggest, proxy transparency, can just have it so that proxies throw when the key they're trapping is a private symbol.

As for passing a reified private symbol through a membrane, he could decide to throw. Or just let it pass through, since it can't be used to extract data from the other side of the membrane (the membrane will throw on private symbol properties, per above).

Separately, I think that it'll be lighter-weight for people to adopt # than to switch to computed property syntax, even with the improvements in this proposal.

We can both have private symbols and # syntax. We just need to change branding semantics into normal prototype-lookup property semantics. I would love to get rid of the branding semantics anyways.

are you talking about Realms proposal? If yes, what problems could be caused with it and Symbol.private implemented together?

Yup. See the first part of my comment.

littledan commented 5 years ago

See the notes of the September 2018 TC39 meeting where we discussed this issue. For example, about making private symbols work, @erights said at that meeting:

MM: There's nothing we can do to solve this with membranes. We tried, and it was a horrible mess. I'm not at all confident that this is possible.

Have you convinced @erights of your ideas above? I'm pretty sure the idea there is within the spectrum of things that was considered in the ES6 cycle and rejected.

jridgewell commented 5 years ago

Have you convinced @erights of your ideas above?

No, but that' doesn't change that it's possible. See https://github.com/zenparsing/proposal-private-symbols/issues/7#issuecomment-424859518.

Note, too, that in the last TC39 meeting we were discussing this under the assumption of transparent proxies (where proxy[privateSymbol] would skip the proxy and go directly to the target). We can make proxies non-transparent (where they throw without consulting the proxy's hander nor the target), and they become nearly identical what would happen with private fields. This would also solve MM's goals.

There are really 3 orthogonal choices:

  1. Proxy transparency
  2. # Syntax vs [ ] computed
  3. Branding vs prototype-lookup

Private Fields chose non-transparency, # syntax, and branding. Symbols (as we discussed in the last meeting) chose transparency, [ ] syntax, and prototype lookup.

But Proxy transparency is an orthogonal decision to encapsulation. Private symbols can choose non-transparency to solve the MM's goals. I'm fine with doing whatever will satisfy membranes. I do not want an incorrect idea about proxy transparency to force our hand on branding.

That leaves us with the last two choices. Class fields choose # syntax, but private symbols can choose either (where #priv syntax would mean the [priv] where priv is a lexical binding). We can even use the current class fields syntax without changes and have it work with symbols (the field #priv = 1 declaration would inject priv into the lexical scope).

But branding is also an orthogonal decision to both proxies and syntax. This decision affects what we can reify to. If we choose branding, symbols just don't make sense. But if we choose prototype-lookup, we can reify to either a symbol or a (modified) PrivateName. If we choose to stick with # syntax, we don't even have to choose the reify yet, it can be deferred to decorators later.

littledan commented 5 years ago

Personally, I would be fine with a follow-on proposal that reifies private symbols, with private symbols having the semantics that they are not transparent through Proxies and do not do prototype lookup. I see this as actually compatible with "branding"--we could include the same kinds of exception throwing behavior with [] syntax, if we figure out some way to differentiate the Set and Initialize operations. Such a follow-on proposal would "layer" well underneath this proposal.

A few issues with this proposal, which led me to not pursue it in this repository:

hax commented 5 years ago

@Igmat

even though Syntax 2 looks like classes 1.1 it has very different meaning

Yeah I know the difference. I just want to say use let for all-instance shared state does not match the expectation of most js programmers. I think you may want to use static instead of let/const in your cases.

hax commented 5 years ago

@shannon

I know people are going to say it needs to be syntax to avoid monkey patching Symbol

If you never export (leak) the private symbol, there would be no way to monkey patch.

shannon commented 5 years ago

@hax I meant monkey patching Symbol.private. So when Symbol.private is called it's not the original function and you can capture all private fields.

Igmat commented 5 years ago

@littledan

Private methods wouldn't work seamlessly--you'd need a decorator or something like it to set them up nicely.

Could you please clarify this?

# syntax would still be more beginner-friendly and lead to higher adoption (so, you'd want both private symbols and # syntax).

It's very arguable. Do you have any facts (e.g. result of some poll) behind such statement?

Symbol doesn't have all of the "integrity" that PrivateName does, leading to questions of capability leaks in practice (but this comes up less frequently since you don't use methods to read and write symbols)

Are you talking about returning symbol from a method or something like this?

Such a proposal wouldn't meet some invariants that @erights is maintaining about what it means for something to be a property. By saying that private fields are like a WeakMap instead of like a property, those invariants are maintained. If we're not maintaining property invariants, the [] syntax could be seen as misleading. @erights could probably explain the details better than I could.

What invariants aren't maintained? Is https://github.com/zenparsing/proposal-private-symbols/issues/7 one of them? You may read my last comment about it and why it's not an issue of private symbols at all.

jridgewell commented 5 years ago

@littledan:

I would be fine with a follow-on proposal that reifies private symbols, with private symbols having the semantics that they are not transparent through Proxies and do not do prototype lookup. I see this as actually compatible with "branding"...

I think it'd be very weird if we had a private symbol not do normal prototype lookup.

we could include the same kinds of exception throwing behavior with [] syntax, if we figure out some way to differentiate the Set and Initialize operations. Such a follow-on proposal would "layer" well underneath this proposal.

Unfortunately, this is what branding requires. It's also the reason I don't like branding. I'd rather private symbols behave like normal properties (normal prototype lookup, I can add private symbol to any object, etc).

# syntax would still be more beginner-friendly and lead to higher adoption (so, you'd want both private symbols and # syntax).

Agreed. There's less overhead with having to remember that the identifier inside the [ ] is tied to a private symbol, and not some other value.

Symbol doesn't have all of the "integrity" that PrivateName does, leading to questions of capability leaks in practice (but this comes up less frequently since you don't use methods to read and write symbols)

Can you explain? I think symbols would immediately meet the Level 1 security we discussed with PrivateName. Level 3 would be securing the other prototype methods, but I think we agreed those weren't strictly necessary. There are still certainly Level 2 leaks, but that's inherent in reifying it and not using pure syntax.

If we're not maintaining property invariants, the [] syntax could be seen as misleading. @erights could probably explain the details better than I could.

I could definitely see resistance to them if they behaved like you described. I think if we choose private symbols, they should behave like real symbols.


@Igmat:

What invariants aren't maintained? Is zenparsing/proposal-private-symbols#7 one of them? You may read my last comment about it and why it's not an issue of private symbols at all.

@littledan is discussing something different here. But you're comment in https://github.com/zenparsing/proposal-private-symbols/issues/7 is incorrect. I'll follow up there.

hax commented 5 years ago

@Igmat

but it seems that lack of simple and ergonomic syntax for declaring private properties with it is a deal-breaker for committee

I think zenparsing already said, and I agree him, that most js programmers don't have eager desire to "hard private", only some framework authors want that. In my opinion, simple and ergonomic syntax only important if most people would use it. And I think solution based on private symbol (this[symbol]) could never be worse than current this.#priv, and framework authors always can use typescript-like syntax and use transpiler to compile it to private symbol. You know I support Classes 1.1. But I also believe Symbol.private could be a solution far better than current proposal.

hax commented 5 years ago

@shannon

I meant monkey patching Symbol.private. So when Symbol.private is called it's not the original function and you can capture all private fields.

Maybe we need a PrivateSymbol which is non-writable and non-configuarable on global. Another possibility is using import PrivateSymbol from '@std/private' (I'm not sure what syntax is standard lib proposal using, just use @std/private as a placeholder.)

shannon commented 5 years ago

@hax if those are technically possible then sure, I would be ok with this. I know there has been resistance to the idea of freezing global objects but I'm not sure of all the details.

glen-84 commented 5 years ago

I haven't read everything there is to read about private symbols, but already it seems like a much better direction to take. Reading this was like music to my ears.

In the past I suggested making the syntax even less readable, and leaving the syntax sugar to TypeScript/Babel. Private symbols could solve this in a similar way – add the core "primitive" of private symbols to the language (with no syntax sugar initially), and see how transpilers (and developers) make use of them.

Syntax sugar can be added to the language itself at a later date, once more data is gathered regarding usage patterns. In this way, the required functionality will exist, but the decision regarding the exact syntax can be deferred. Also, limiting the scope of the proposal will make it easier to reach consensus.

Igmat commented 5 years ago

Something weird happens with github, so I'm not sure that one of my comments (https://github.com/tc39/proposal-class-fields/issues/100#issuecomment-431839549) was actually seen by anybody, and since it's very related to Private.symbol topic, I'll copy it here:

@littledan

The alternative/solution is no syntax IMO.

We have considered private symbols in depth, both in the ES6 cycle and at the previous TC39 meeting, but both times concluded that we should not adopt them and instead go in this private field declaration direction.

I read meeting notes and have few notes about it:

WH: My concern is is about encapsulation/privacy, and you claim that you can separate it from branding. That kinda works if you only ever have one private field per class, but this is what happens if you have multiple private fields in a class that you want to keep consistent: WH: This doesn't achieve encapsulation, which is kind of the whole point of this feature. WH: It doesn't achieve encapsulation when the class has more than one private field.

If somebody like WH needs brand-checking to keep internal state consistent (while it's definitely a way to do so, there are others that don't require brand-checking), he/she may just create simple helper function or decorator (~20 lines of code) to add brand-checking capabilities to class/method/property.

MM quotes like:

MM: There's nothing we can do to solve this with membranes. We tried, and it was a horrible mess. I'm not at all confident that this is possible.

While I appreciate @erights work on Membrane pattern - this pattern isn't so common and valuable to dismiss any other alternatives if they doesn't fit Membrane goals. But what is the most important - proper Membrane is still achievable with Symbol.private (take a look at https://github.com/zenparsing/proposal-private-symbols/issues/7#issuecomment-424859518). Obviously, that sample isn't full implementation, but if Membrane is indeed so important for committee, I can create a project with Membrane implementation assuming Symbol.private existence. The only downside of such implementation comparing to Membrane without Symbol.private support is a little bit bigger memory consumption and worse performance in edge cases, when we return Symbol.private from some method/property.

Joyee: During code review, we tend chose symbols when we need to add private things. We used to use “_private”, but they were impossible to deprecate. New contributors still use _private even years after symbols became available. We need linters to fix some of these, but we still need humans to catch it. But we still can't tell people to just use symbols because they're not used to them. If symbols are available now, and people still don't use it, then private symbols aren't going to improve this situation.

This could be solved by shorthand syntax provided here #149

SYG: The syntax is really private. Allowing an escape hatch makes thing much harder to reason about.

It's not an escape hatch, it's separate feature for providing some sort of friend relation between different classes.

Conclusion/Resolution ?

There are no conclusion, but you still just dismiss an alternative. Obviously, you can't have solid alternative if you don't give it a chance to raise up and cover all cases.


What choice would they have?

They don't have to ever like the feature or use it; that remains the choice.

I hope it's a joke. Whenever you land some sort of private (either # or Symbol.private), there will be a lot of libraries using them, and lots of apps that do the same. If some library author will choose to not use such privates, he will have to understand that his users may and will use such privates, so his library should know how to work with them (and as I already said reactive properties couldn't be implemented if # is used).

Conclusion

We MUST invest some additional efforts into alternatives, otherwise stage 3 === stage 4, if there are no chance to change something for proposal in this stage.

Obviously, we'll have to invest more time for proposal at early stages to avoid problems like we have now. But if such mistake already happened we MUST be brave enough to confess it and do everything to fix it, while what is going on right now looks like ignoring the problem.

trusktr commented 5 years ago

We can already do

const somePrivate = Symbol()

class Foo {
  constructor() {
    this[somePrivate] = 'foo'
  }
}

so this syntax doesn't seem like it adds anything incredibly useful.

Just having computed property names would be good enough:

const somePrivate = Symbol()

class Foo {
  [somePrivate] = 'foo'
}

without the extra symbol somePrivate syntax. Plus personally I find it additionally awkward that the symbol is declared in the class definition and is use with no explicit receiver.

ljharb commented 5 years ago

Symbols are not private, they are fully public, so no, that's not good enough.

trusktr commented 5 years ago

@ljharb Symbol.private() would be useful then... but that alone is not good enough for those who want it to be as declarative as possible (defined on the class).

Why can't we start with imperative then add declarative sugar later?

Sure someone could monkey-patch Symbol, but honestly saying who's going to do that?

IMO this is about code integrity (vs strict security, which would be nice), and I am willing to bet that the vast majority of people will never ever want to monkey patch Symbol.

IMO stage 3 is too far for this proposal. As seen here and in other issues, many people still have many opinions about it. After class and import syntax from ES6, private fields is probably the third-most anticipated new feature (I'm only guessing, but there's lots of opinions and discussion in this repo).

ljharb commented 5 years ago

Private symbols have other issues which makes them unlikely to proceed; separately, anything that is possible, someone will do (not that monkeypatching Symbol is the concern).

edit: nvm (This proposal is stage 3, not 4)

trusktr commented 5 years ago

Corrected my post to "stage 3" (EDIT: but I'd be happy to use private fields regardless of the final mechanics anyways)

hax commented 5 years ago

Private symbols have other issues which makes them unlikely to proceed;

Could we know what's the issues?

separately, anything that is possible, someone will do (not that monkeypatching Symbol is the concern).

Agree. But that only means we need some mechanism to make private symbol always truly private. For example, introduce const x = private() --- make private as a pseudo function like import() and returns private symbols. This is may not the best option, but I believe there are many options if we'd like to investigate.

Igmat commented 5 years ago

@ljharb continuing discussion from https://github.com/tc39/proposal-class-fields/issues/162#issuecomment-440302684 this https://github.com/tc39/proposal-class-fields/issues/149#issuecomment-431860782 wasn't answered.

In short: all issues raised in your discussion at that meeting are solvable (do I have to add more proves for this statement?), but Symbol.private is still dismissed. Why? If only question is a syntax, I may work on proposal which includes short-hand syntax for private symbol and also includes public properties (with same syntax as in this existing proposal). Does it make any sense? I just don't want to waste few weeks of my life to be rejected in the end, only because existing proposal already landed.

ljharb commented 5 years ago

I believe the concern is that having a private symbol means the key itself is a first-class value that can be passed between membranes independently of objects themselves - but this isn’t my area of expertise; it’s just my interpretation of why after 6-8 years of discussion, private symbols seem to remain a nonstarter.

Igmat commented 5 years ago

@ljharb ok, so who is capable of answering to this? @littledan or may be @erights?

shannon commented 5 years ago

@ljharb I've also been a bit confused by PrivateName in decorators. This seems like the exact same problem to me. So again, difficult to understand importance. Why do we consider it ok for decorators but not for membranes?

ljharb commented 5 years ago

Choosing to decorate a private field is the same as choosing to expose the existence and contents of that field, just like if you made a function that did so and then exposed it publicly. It’s not that exposure is a problem, it’s that it shouldn’t be the default, and it must be possible not to expose it.

Igmat commented 5 years ago

it must be possible not to expose it.

In Symbol.private it's possible, just don't return private symbol from public methods.

shannon commented 5 years ago

@ljharb it is perfectly possible to not expose a private symbol. Just don't expose it. I really don't understand this logic.

Igmat commented 5 years ago

@ljharb, @littledan, @erights could you, please, answer questions in this topic or at least give some ETA for such answers?

Of course, you don't have to answer any questions from community (and I'm part of it) - if you don't want to participate in this topic, please, inform me, so I can stop wasting my time.

erights commented 5 years ago

@Igmat Which questions? The question about private symbols vs membranes was answered years ago, and many times since then. In this thread above, I didn't find any new questions from a brief skim.

Do you have in mind any new questions? Could you summarize them? Thanks.

If you just need links to old answers to old questions (such as why private symbols break membranes), let us know and we can try to track them down.

hax commented 5 years ago

@erights Actually I have no idea why intentionally exposing of private symbol is unacceptable for membranes and intentionally exposing private slots by decorators is acceptable for membranes.

Forgive me. I have used many time to understand membranes for no other reason, just like @Igmat did. And if I failed to understand that, I am sure 99.9% javascript programmers will fail too.

Such issue may obvious to you, but it's very obscure for at least 99.9% js programmers.

And we should agree membrane usage is rare in the whole ecosystem. But the footguns of current proposal are obvious to 90%+ javascript programmers. I don't understand why membrane should have the highest priority and the issues the objectors raised always the lowest. Maybe just because you are in the TC39, and we are not?

Don't want to be offensive, I try to believe all members in TC39 have the good faith that you are doing right thing. Just try to give you the viewpoint from outside of TC39.