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

Reply via email to