using Private name objects for declarative property definition.

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Jul 8 12:16:56 PDT 2011


The current Harmony classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes includes the concept of private instance members and syntax for defining them.  While it presents a syntax for accessing them (eg, private(foo).bar accesses the private 'bar' member of the object that is the value of foo) there does not yet appear to be consensus acceptance of this access syntax. There also appears to be a number of still unresolved semantic issues WRT such private instance members.  As an alternative, the classes proposal says that  "a pattern composing private names with classes can satisfy" the requirements for private instance members. However, so far, we have no concrete proposals on what these patterns might be.  This is a first cut at proposing such patterns.


The current versions of the private names proposal http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects  simply exposes a constructor for creating unique values can be be used as property keys:

    const key = Name.create();

Those values can be used to define and access object properties:

    let obj = {};
   print(obj.key);     // "undefined"
   obj[key]="some private state";   //create a property with a private name key
   print(obj.key);    // still "undefined"
   print(obj["key"]); //"undefined"
   print(obj[key]);   // "some private state"

In the above example the property created by using the private name value is essentially an instance private member of  obj.  In general, various levels of visibility (instance private, class private, friends, module private, etc.) can be achieved simply by using lexical scoping to control access to the variable that contain private key values. 

For example:

   function ObjWithInstancePrivateMember(secret) {
        const s = Name.create();
        this[s]= secret;
        this.method = function () {
            return doSomething(this[s]);
        }
   }

   const priv = Name.create();
   function ObjWithClassPrivateMember(secret) {
        const s = Name.create();
        this[priv]= secret;
        this.method = function (another) {
            return doSomethingTogether(this[priv], another[priv]);
        }
   }

Note that  [ ] notation with an expression that evaluates to a private name value must be used to access such private members.  There have been various proposals such as http://wiki.ecmascript.org/doku.php?id=strawman:names and  http://wiki.ecmascript.org/doku.php?id=strawman:private_names to allow dotted property access to be used with private name keys.  However, these either had technical problems or their semantics were rejected by community discussion on this list.  The issues raised in response to those proposals are likely to recur for any private member access syntax that uses dot notation.

A weakness with using private names object to define private members is that currently there is no way to declaratively define objects with such members. Consider the following definition of a Point abstraction using extended object literals and a simple naming convention to identify "private" members:

const Point = {
     //private members
     __x: 0,
     __y: 0, 
     __validate(x,y) { return typeof x == 'number' && typeof y = 'number'},
     //public members
     new(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          return this <| {
                  __x: x,
                  __y: y
                 }
      };
     add(anotherPoint) {
           return this.new(this.__x+another.__x, this.__y+another.__y)
     }
}

using private name objects this would have to be written as:

const __x=Name.create();
const __y=Name.create();
const __validate=Name.create();
const Point = {
     //public members
     new(x,y) {
          if (!this[__validate](x,y)) throw "invalid";
          let obj = this <| { };
         obj[__x] = x;
         obj[__y] = y;
         return obj;
           };
     add(anotherPoint) {
           return this.new(this[__x]+another[__x], this[__y]+another[__y])
     }
}
 //private members for Point
Point[__x] = 0;
Point[__y] = 0,;
Point[ __validate] = function (x,y) { return typeof x == 'number' && tyupeof y = 'number'};

The biggest issue with this rewrite is it loose the declarative definition and replaces it with an hybrid that has some declarative parts and some imperative parts. This breaks the logical encapsulation of the definition of the Point abstraction making it harder to be understand and manipulated by both humans and mechanical tools.  The reason this is necessary is that object literal notation currently has no way to express that a definition of a property uses a name that needs be interpreted as a reference to a lexical variable whose value is the actual private name object.  Fixes these requires an additional extension to object literals.

One possibility, is to use a prefix keyword to each property definition that uses a private key value.  For example:

const __x=Name.create();
const __y=Name.create();
const __validate=Name.create();
 Point = {
     //private members
     private __x: 0,
     private __y: 0, 
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'},
     //public members
     new(x,y) {
          if (!this[__validate](x,y)) throw "invalid";
          return this <| {
                  private __x: x,
                  private __y: y
                 }
      };
     add(anotherPoint) {
           return this.new(this[__x]+another[__x], this[__y]+another[__y])
     }
}

Note that the separate definition of the private names is still required. In the above example, prefixing a property definition with 'private' means that the identifier in the property name position is evaluated as a lexical reference and its value is used as key of the property that is being defined.  (If it is not a private name object, ToString of the value is used as the key). There are a couple potential issues with this approach.  The 'private' keyword is very noticeable, but its meaning is different from what might be expected from readers familiar with widely used languages. It particular, it does not control the actual  accessibility of the property.  It only means that the property key (may) be a private name value.  The actual accessibility of the property is determined by the visibility of the private name value.  Another potential issue is that is precludes the use of 'private' for other purposes. For example, a possible short hand for:

const __x=Name.create();
const __y=Name.create();
const __validate=Name.create();

might be:

private __x;
private  __y;
private __validate;

and if this was available, it might be be useful to allow such definitions to occur inside an object literal so that private name object definitions could be logically bundled in the object declaration that uses them. The use of 'private'  as a property prefix might preclude this and other usages of 'private'.

Another alternative that avoids using the 'private' prefix is to allow the property name in a property definition to be enclosed with brackets:

const __x=Name.create();
const __y=Name.create();
const __validate=Name.create();
 Point = {
     //private members
     [__x]: 0,
     [ __y]: 0, 
     [__validate](x,y) { return typeof x == 'number' && typeof y = 'number'},
     //public members
     new(x,y) {
          if (!this[__validate](x,y)) throw "invalid";
          return this <| {
                  [__x]: x,
                  [__y]: y
                 }
      };
     add(anotherPoint) {
           return this.new(this[__x]+another[__x], this[__y]+another[__y])
     }
}

The meaning is the same as the private prefix, the identifier enclosed in brackets is evaluated as a lexical reference and its value is used as the key of the property that is being defined.  Using brackets in this manner would enforce the fact that private name keyed properties can only be accessed by [ ] property access syntax.

Of course some people may object to this requirement, that bracket access must be used to access private state properties.  One possible objection is that [ ] suggests array access or more generally computed property access where the actual property accessed is likely to vary between different executions of an expression containing them.  A fix for this could be to use a different notation for private name based property access.  For example, obj at foo could be defined to mean the same thing as obj[foo] if foo's value is a private name object.  If that notation was used for property accesses it could also be used for property declarations.  For example:

const __x=Name.create();
const __y=Name.create();
const __validate=Name.create();
 Point = {
     //private members
    @__x: 0,
    @__y: 0, 
    @__validate(x,y) { return typeof x == 'number' && typeof y = 'number'},
     //public members
     new(x,y) {
          if (!this at validate(x,y)) throw "invalid";
          return this <| {
                  @__x: x,
                  @__y: y
                 }
      };
     add(anotherPoint) {
           return this.new(this at __x+another@__x, this at __y+another@__y)
     }
}

Each of these approaches seem plausible.  For a side-by-side comparison of the above example using the alternatives see http://wiki.ecmascript.org/lib/exe/fetch.php?id=harmony%3Aprivate_name_objects&cache=cache&media=harmony:private-name-alternatives.pdf .  I'm interested in feedback on the alternatives.

I've only used object literal in these examples, however the same alternatives should be equally applicable to class declarations.

Allen






-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110708/86efd65d/attachment.html>


More information about the es-discuss mailing list