Let me see if I can roll this up: you're looking for syntax support for something akin to Java and C#'s "internal" privilege level, and a means to declare a "common private name" so that something like this will work:
```js var {A, B, C} = (() => { static common = Symbol("common"); class A { internal [common] = 1; sum(...arg) { var retval = this#[common]; for (let obj of arg) { retval += obj#[common]; } return retval; } } class B { internal [common] = 2; sum(...arg) { var retval = this#[common]; for (let obj of arg) { retval += obj#[common]; } return retval; } } class C { internal [common] = 3; sum(...arg) { var retval = this#[common]; for (let obj of arg) { retval += obj#[common]; } return retval; } } return { A, B, C }; })(); (new A()).sum(new B(), new C()); //If it works, returns 6 ``` It's already the case that A, B, & C will have to include the `[[DeclarationInfo]]` of the arrow function in each of their respective methods' `__proto__` chains. That and the `Symbol` for direct use as a private name, solves the problem, and avoids the original issue without much of a change. The "internal" keyword would be trickier to implement as it would require that the corresponding declarations be added to the `[[DeclarationInfo]]` of the containing scope while the storage exists in the `[[PrivateValues]]` of the owning objects. It's not impossible, possibly not even difficult. I'll have to give it some thought. On Sat, Jul 28, 2018 at 11:24 PM Darien Valentine <valentin...@gmail.com> wrote: > > What you're essentially asking for is a violatable private field, or as > has been described by others, a "soft private". > > We might have different definitions here, but I would describe what I’m > talking about as hard private. Soft private, at least as it appears to have > been defined in [prior discussions]( > https://github.com/tc39/proposal-private-fields/issues/33), described an > avenue where symbol keyed properties were given a new syntactic form — but > they were still just regular symbol keys, and therefore could be > introspected by outside agents who had not been given express privilege to > do so: > > > [...] the core would be that "private state" is simply (public) > symbol-named properties, with syntactic sugar for those symbols, and > possibly some kind of introspection over them [...] > > The thread goes on to contrast the soft model with an earlier version of > the private fields proposal seen today. The hard private example uses the > class declaration as a pseudo-scope, but contrasting these two options as > if they are binary is not accurate: hard private through > module/function/block scope already exists, it is just difficult to work > with in the context of shared prototypes — one must either use WeakMaps, > technically giving _hardness_ because of the forgeability of > `global.WeakMap` / `WeakMap.prototype` / `WeakMap.prototype.get|has|set`, > or be willing to either not worry about garbage collection or implement it > manually. This could be solved for with a few rather undramatic changes, > though. > > Notably, the first post there lists the following as a disadvantage of the > soft model it describes: > > > Platform objects, both within ECMAScript and in embedding environments, > contain hard private state. If a library wants to be high-fidelity and just > like a platform object, soft-private state does not provide this (@domenic) > > ...but neither model there quite covers that use case. Platform objects > _can_ see each other’s private state (cf the `isView` example earlier, or > scan the DOM API specs / Chrome source a bit to find numerous examples). > It’s only the ES layer interacting with their interfaces that cannot. > > Such things can be achieved with ordinary scope, which is why the WeakMap > pattern has worked in practice in my experience to date, while > class-declaration-scoped privacy has not. It isn’t uncommon for a library’s > exposed interface to be composed of an object graph, where privacy is a > concern at this public interface level, but library internal state may be > interconnected in unexposed ways under the hood. The most familiar example > of this is a DOM node tree. As an experiment, perhaps try to implement the > relationships between HTMLFormElement, HTMLFormControlsCollection and the > various form control elements using either the main private fields proposal > or your alternative proposal and see what happens. > > > However, the guardian logic tries to verify that the function trying to > access the private fields of an instance is a member of the same or > descending prototype that was used to create that instance. > > Because I’m looking at this in terms of slots, I’d first point out that > prototypes don’t determine slottedness, the execution of some specific > constructor does. It’s during this process that slots are associated with > the newly minted object by its identity. But even the current private > fields proposal tracks this behavior closely, and I’m not sure how else it > could work. The [[Prototype]] slot of an object is typically mutable > (`R|O.setPrototypeOf`, `__proto__`) and forgeable (Proxy’s `getPrototypeOf` > trap). Why/how would its value matter when it comes to accessing private > state? > > ```js > const pattern = /foo/; > Reflect.setPrototypeOf(pattern, Date.prototype); > pattern instanceof Date; // true > pattern instanceof RegExp; // false > pattern.getMinutes(); // throws TypeError because [[DateValue]] slot is > missing > RegExp.prototype.exec.call(pattern, 'foo'); // works; object has RegExp > private slots > ``` > > > If I removed that requirement, it would work. However, there'd be no way > to keep the private data from being leaked. Sadly, it's all or nothing with > this approach. Hard private or soft private, those are the only choices. > > In the context of what you’ve described here this may be true, but no such > limitation presently exists. We can already do all this — hard, leak-free > privacy, brandedness, “friends” etc — with scopes and WeakMaps, but for the > fact that the `WeakMap` intrinsics may be forged. So what’s baffled me is > this: why are all the proposals exploring this space not addressing that > relatively simple existing problem, and instead starting off from a place > of significant new complexity? You said “maybe after the private fields > problem has been resolved, someone will figure out a better way to handle > your use cases,” but I’d have hoped for the opposite — I want the primitive > building blocks which things like class field syntax could be built over, > if it is found that they are still necessary once the root issue is solved > for. > > > The main reason the privacy is set on a declaration level is because > scope-level inheritance isn't very good for class-oriented inheritance. > > Can you explain this more? I’m not sure what’s meant by “scope-level > inheritance” here. > > > I don't intend to stop [...] > > I very much admire your dedication! I’m also digging the discussion. I > think we may be representing viewpoints at opposite extremes here, so it’s > an interesting contrast, but it also probably means we may be lacking some > context for understanding one another’s angles. I’d be curious to hear more > about what you see as the problems with the current fields proposal + how > your members proposal would solve them; the repo readme didn’t seem to > include a rationale section. > > On Sat, Jul 28, 2018 at 10:30 PM Ranando King <king...@gmail.com> wrote: > >> I've almost given up on making any significant headway in either >> adjusting or flat-out correcting the flaws in that proposal, but I don't >> intend to stop trying until either we get stuck with that proposal, or they >> understand and accept what I'm telling them, or logically prove that my >> concerns are either irrational or inconsequential. >> >> > Private object state in particular is only _made complex_ by >> associating it with declarations instead of scopes that happen to contain >> declarations (or into which constructors are passed, etc). The complexity >> is artificial — not a good sign imo. >> >> That's not quite right. What you're essentially asking for is a >> violatable private field, or as has been described by others, a "soft >> private". Since we agree that the "friendly" & "befriend" pair is a >> somewhat (if not completely) bad idea, I'm going to take 1 more pass at >> your 3 requests with a different angle. >> >> > Adding the same “slot” to multiple classes which don’t inherit from >> each other >> > Selectively sharing access to private state through functions declared >> outside the class body >> >> ```js >> //Using my proposal >> var {A, B, C} = (() => { >> const common = Symbol("common"); >> >> class A { >> private [common] = 1; >> add(...args) { >> var retval = this#[common]; >> for (let obj of args) { >> retval += obj#[common]; >> } >> return retval; >> } >> } >> class B { >> private [common] = 2; >> optional() { >> console.log(`common member = ${this#[common]}`); >> } >> } >> var C = { >> private [common]: 3, >> required() { >> console.log(`common member = ${this#[common]}`); >> } >> } >> >> return { A, B, C }; >> })(); >> >> //So you want the following statement to not throw a TypeError and return >> 6 >> (new A()).add(new B(), C); >> ``` >> I'm not sure I can make this work in my proposal, and I'm absolutely sure >> you'd be flatly refused by the other proposal. If a `Symbol` is provided as >> the `[[IdentifierName]]` of a private or protected field, then I can let >> that `Symbol` be both the key and value that are added to the >> `[[DeclarationInfo]]` and `[[InheritanceInfo]]` records. That way there >> will be a common private field name usable by all 3 objects. However, the >> guardian logic tries to verify that the function trying to access the >> private fields of an instance is a member of the same or descending >> prototype that was used to create that instance. If I removed that >> requirement, it would work. However, there'd be no way to keep the private >> data from being leaked. Sadly, it's all or nothing with this approach. Hard >> private or soft private, those are the only choices. The TC39 board has >> already decided that what they want new syntax for is hard private. >> >> > Adding slots dynamically, e.g. when adding mix-in methods that may >> initialize a new slot if necessary when called, since subclassing is not >> always appropriate >> >> Because the TC39 board has set their sights on hard private, this will >> require new syntax like what I suggested earlier Adding private members >> dynamically would also pose a leak risk if it could be done after the >> prototype has been fully constructed. The main reason the privacy is set on >> a declaration level is because scope-level inheritance isn't very good for >> `class`-oriented inheritance. The `class` keyword was provided to simplify >> the vertical inheritance model, along with some API to enable inheritance >> from native objects even without using `class`. The syntax changes for >> simplifying private field declaration are just an extension of that. Even >> though it's not unusual for some developers to spend a lot of time working >> with fringe use-cases, syntax changes are almost always going to be made >> for the most common use cases first. Maybe after the private fields problem >> has been resolved, someone will figure out a better way to handle your use >> cases. >> >> >> On Sat, Jul 28, 2018 at 3:52 PM Darien Valentine <valentin...@gmail.com> >> wrote: >> >>> > Are you saying you want multiple non-hierarchally related classes to >>> have an instance private field with shared name [...] >>> >>> Yeah. This is a hard problem to solve when trying to integrate private >>> fields with class syntax, but it’s not a problem at all when privacy is a >>> more generic tool based on scope. This also isn’t a foreign concept in ES: >>> consider this intrinsic method: >>> >>> https://tc39.github.io/ecma262/#sec-arraybuffer.isview >>> >>> This method returns true if the argument has the `[[ViewedArrayBuffer]]` >>> slot. This slot exists on genuine instances of both `%TypedArray%` and >>> `%DataView%`, but they do not receive these slots by way of inheritance >>> from a common constructor. There are similar cases in HTML host APIs. >>> >>> > The befriend keyword would allow an object to request friendship with >>> an existing friendly object. I'm not sure this is a good idea, though. >>> >>> I don’t think it is either, no. It’s too much complexity for too little >>> gain. But again, this is achievable “for free” just by divorcing “private >>> object state” from class declarations (or object literals). I would ask: >>> what problem is solved by making this a feature of the declarations >>> themselves? Does it merit the complexity and the hoop jumping needed to >>> handle edge cases?\* >>> >>> \* One person’s edge case; another’s everyday concern haha. >>> >>> > The example you gave above still declares the functions in question >>> inside the class body, so that's not really a solution. >>> >>> If you’re referring to the first example, that is a demonstration of >>> what is possible using the existing stage 3 class fields proposal as >>> implemented in Chrome. It isn’t what I want; it’s what’s necessary to >>> achieve this with the current stage 3 proposed model. >>> >>> > Sounds to me like you'd love for class syntax to look like this >>> [[example with mixin syntax in declaration]] >>> >>> Perhaps — it’s interesting for sure! But the pattern that already works, >>> `mixin(Cstr)`, is not presently a source of problems for me. Private object >>> state in particular is only _made complex_ by associating it with >>> declarations instead of scopes that happen to contain declarations (or into >>> which constructors are passed, etc). The complexity is artificial — not a >>> good sign imo. >>> >>> > One thing both proposal-class-fields and proposal-object-members have >>> in common is that the focus is on producing instance-private fields. All 3 >>> of the scenarios you presented lay outside of that focus for one reason or >>> another. >>> >>> Both the WeakMap solution and the stub concept I provided after are more >>> generic than privacy in either of those proposals. When I say "object >>> private state," it’s true that the object in question could be any object. >>> But in practice, any realization of the feature would pertain chiefly to >>> class instances, and the examples I gave, though contrived, do concern >>> class instances. The reason private object state is chiefly an issue of >>> class instances stems directly from the nature of prototype methods and >>> accessors, so if you are not making use of prototypes, you could instead >>> have used a closure+factory directly. >>> >>> --- >>> >>> In a nutshell, my issue with existing proposals could probably be >>> summarized as a concern that they are neither as generic nor as simple as >>> native slots. To be clear, proper “slots” are an internal concept, only >>> observable indirectly — but they are the special sauce underlying a number >>> of behaviors which are presently awkward to achieve in ES code itself, and >>> they are a nice simple model of private object state which is tantalizingly >>> close to, but not _exactly_ the same as in two critical ways, symbol keyed >>> properties. That said, “real” slots would continue to have an advantage >>> with regard to cross-realm stuff even if private symbol keys existed. >>> >>> That such a model is radically simpler — minmax and all that — feels >>> very important to me, but I dunno. I’m not holding my breath for big >>> changes here. The current stage 3 proposal seems to be unstoppable; much >>> smarter / more important people than me have already tried and failed. :) >>> >>> >>> On Sat, Jul 28, 2018 at 3:14 PM Ranando King <king...@gmail.com> wrote: >>> >>>> In a word... wow. You've got me thinking hard here. Those are some >>>> peculiar use cases, and they do a great job of highlighting why someone >>>> might forego using `class`. One thing both proposal-class-fields and >>>> proposal-object-members have in common is that the focus is on producing >>>> instance-private fields. All 3 of the scenarios you presented lay outside >>>> of that focus for one reason or another. >>>> >>>> > Adding the same “slot” to multiple classes which don’t inherit from >>>> each other >>>> >>>> I'm a little confused by this one. Are you saying you want multiple >>>> non-hierarchally related classes to have an instance private field with >>>> shared name, such that the same private field name refers to a distinct and >>>> separate field on each instance of every such class, but where any such >>>> instance can have that field referenced by that shared name from any member >>>> function of the corresponding classes? (Wow that was wordy to write out...) >>>> If this is what you meant, you're describing friend classes. The top-down >>>> processing nature of ES makes this a difficult thing to create a clean >>>> syntax for without risking leaking the private state or fundamentally >>>> altering how ES is processed. Mutual friendship is even harder. >>>> >>>> ... and yet I just thought of a way to do it. By telling you this I'm >>>> leaving myself to consider writing a proposal containing 2 new keywords: >>>> `befriend` and `friendly`. I don't know if this can be done with the >>>> existing proposal being what it is. However, with my proposal, there's a >>>> chance. The `friendly` keyword would declare that an object is prepared to >>>> share select information with any object that befriends it. The `befriend` >>>> keyword would allow an object to request friendship with an existing >>>> friendly object. I'm not sure this is a good idea, though. This means that >>>> any object declared 'friendly' is automatically insecure as all it takes to >>>> gain access to the selected members of its private space would be to >>>> 'befriend' it. >>>> >>>> > Selectively sharing access to private state through functions >>>> declared outside the class body >>>> >>>> The example you gave above still declares the functions in question >>>> inside the `class` body, so that's not really a solution. If the example >>>> you gave actually solves your use case, then what you're asking for here >>>> isn't even needed. If, however, that was a bad example, then it sounds like >>>> you're looking for friend functions. See the previous section. >>>> >>>> > Adding slots dynamically, e.g. when adding mix-in methods that may >>>> initialize a new slot if necessary when called, since subclassing is not >>>> always appropriate >>>> >>>> Sounds to me like you'd love for `class` syntax to look like this: >>>> >>>> ```js >>>> class [<identifierName1>] [extends <identifierName2>] [mixes >>>> <identifierName3>[, <identifierName3>[, ...]]] { ... } >>>> ``` >>>> so that the private fields of the objects in the `mixes` list are added >>>> to the set of private fields provided by the `class` definition directly. >>>> That would also require another proposal, but I think that can be done >>>> regardless of which instance-private fields proposal gets accepted. >>>> >>>> On Sat, Jul 28, 2018 at 12:49 PM Darien Valentine < >>>> valentin...@gmail.com> wrote: >>>> >>>>> To put this another, much briefer way, here’s a hypothetical model for >>>>> associating private state with objects that would cover me. Privacy would >>>>> be provided... >>>>> >>>>> 1. in the form of symbolic keys whose presence cannot be observed >>>>> (i.e., they would not be exposed by `getOwnPropertySymbols`) >>>>> 2. and which have a syntactic declaration so that one can be sure they >>>>> are really getting private keys (i.e., an api like `Symbol.private()` >>>>> wouldn’t work) >>>>> >>>>> ``` >>>>> const bar = private(); >>>>> >>>>> // alternatively: const #bar; could be anything so long as it’s >>>>> syntactic >>>>> >>>>> class Foo { >>>>> constructor() { >>>>> this[bar] = 1; >>>>> } >>>>> } >>>>> >>>>> // etc >>>>> ``` >>>>> >>>>> The keys would be typeof 'symbol'; the only difference being that they >>>>> are symbols which are flagged as private when created. They would be >>>>> permitted only in syntactic property assignments and accesses. Existing >>>>> reflection utilities would disallow the use or appearance of such symbols >>>>> both to ensure privacy and to maintain the invariant that they are always >>>>> simple data properties: >>>>> >>>>> ```js >>>>> Reflect.defineProperty({}, #bar, { ... }); // throws type error >>>>> Object.getOwnPropertyDescriptors(someObjWithAPrivateSlot); // does not >>>>> include it >>>>> foo[bar] = 2; // fine >>>>> ``` >>>>> >>>>> This is significantly simpler than what’s in flight both in terms of >>>>> syntax and mechanics, which makes me suspicious that I’m probably ignoring >>>>> things that other people find important. However it would bring parity to >>>>> ES objects wrt being able to implement genuinely private slots in userland >>>>> with the same flexibility as what is done internally. >>>>> >>>>> In total, this entails a new primary expression, a boolean flag >>>>> associated with symbol values, and an extra step added to several >>>>> algorithms associated with Object and Reflect. >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss@mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss