BTW, I came up with an alternate proposal for privacy altogether: https://github.com/tc39/proposal-class-fields/issues/115
TL;DR: private symbols that proxies can't see and that can't be enumerated. ----- Isiah Meadows [email protected] www.isiahmeadows.com On Sun, Jul 29, 2018 at 12:23 AM, Darien Valentine <[email protected]> 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 <[email protected]> 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 <[email protected]> >> 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 <[email protected]> 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 >>>> <[email protected]> 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 >>>>> [email protected] >>>>> https://mail.mozilla.org/listinfo/es-discuss > > > _______________________________________________ > es-discuss mailing list > [email protected] > https://mail.mozilla.org/listinfo/es-discuss > _______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

