An "extend" operator is a natural companion to <|

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Jul 18 10:32:24 PDT 2011


I've recently been experimenting with coding both prototypal and class based object definitions using the various syntactic forms that are currently on the table.  Something has emerged from that which has surprised me.  I have never been a big fan of the "extend" method that is provided by a number of JavaScript frameworks. However, based upon my experiments I'm now think that something like extend solves several issues with the declarative object and class declarations that we have recently been discussing.

Just in case there is anyone on this list who isn't familiar with extend, here is a quick explanation. In most frameworks that support it, "extend" is invoked something like this: 
   obj.extend(extensionObj)
It copies the own properties of  extensionObj and makes corresponding own properties for obj.  Exact details about which properties are copied, various error conditions and even the name of the method varies among frameworks.  It is generally has been used as an imperative supplement to ECMASript's built-in prototype inheritance, for example to provide effects similar to multiple inheritance. 

The use cases that interests me are some what different then this current common use. 

But first, I'll cut to the chase. Here is a quick summary of what I'm going to propose:

In addition to  <| we need another operator <& that is similar to the "extend" method in various frameworks.  It replicates the own properties of its RHS operation on its LHS operand.  It provides an easy declarative way to describe the properties that need to be added to an already created object. For example:

obj <& {
   __constDataProp: x,
   method(a) {return a+this._constDataProp},
   get konst() {return this.__costDataProp}
};

adds three properties to obj. 
   
So, on to the use cases that motivates this. In https://mail.mozilla.org/pipermail/es-discuss/2011-July/015792.html  I used an prototypal inheritance pattern in an example.  A slightly simplified version of the example is:

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
                 }
      };
}

In this pattern, the "new" method on a prototype object is used to create instance of the corresponding object abstraction.  It does this by using the <| operator to create the instance as an object whose [[Protoype]] is set to the prototypal instance.  The object literal on the right of the <| lists the per instance state of the new object.  In this case the properties named "__x" and "__y".   This is a nice declarative way to describe the per instance state but it turns out it doesn't generalize very well to multiple levels of inheritance.  If you tried to use this pattern to create a three dimensional point,  it would probably look something like:

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

Note that the "new" method in Point3D had to essentially copy everything that was in the "new" method of Point.  This isn't very good use of inheritance. It also may not work if true private properties are used instead of just a naming convention. What you would really like to do is to let the implementation of "new" in Point do all the work that relates to x and y and only have to include in Point3D code that relates to z. You might be tempted to write it as:

const Point3D = Point <| {
     //private members
     __z: 0,
     //public members
     new(x,y,z) {
          if (typeof z != 'number') throw "invalid";
          return super.new(x,y) <| {
                 __z: z
                 }
      };
}

However, that wouldn't create what was probably intended. Instead of creating a single instance object that inherits from Point3D it creates two objects, the first one has Point3D as its [[Prototype]] and has own properties "__x" and "__y".  The second object has the first object as its [[Prototype]] and as "__z" as an own property.  If a Point4D was created following the same pattern the per instance state of create by each call of Point4D would be spread over three objects.  The problem is that we want to do a super.new call at each level of the inheritance hierarchy to ensure that we do all the necessary initialization without unnecessary code copying.  However, each level is doing a <| which creates a distinct object.  Instead, what we really want to do is create a single object at the top of the inheritance hierarchy and then add additional properties to that object at each inheritance level. If we want to do that declaratively, we need something like the extend operator. For example:


const NewablePrototype = {new() {return { }};  //define a base prototype with a "new" method that creates a new object

const Point = NewablePrototype <| {
     //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 super.new() <& {
                  __x: x,
                  __y: y
                 };
      };
}

const Point3D = Point <| {
     //private members
     __z: 0,
     //public members
     new(x,y,z) {
          if (typeof z != 'number') throw "invalid";
          return super.new(x,y) <& {
                 __z: z
                 };
      };
}

Basically, <| is exactly what we want for declaratively specify inheritance relationships but for actually constructing instances we want <&.

This same issues also shows up with constructor based abstraction.  The constructor equivalent of Point and Point3D might look like this without extend:

function Point(x,y) {
   this.__x = x;
   this.__y = y;
};
Point.prototype.__validate(x,y) { return typeof x == 'number' && typeof y = 'number'};

function Point3D(x,y,z) {
   if (typeof z != 'number') throw "invalid";
   Point.call(this,x,y);
   this.__z = z;
};
Point3D.prototype = Point.prototype <| {  //need to wire up prototype inheritance
   construtor: Point3D}    //and link prototype back to constructor
};

Note that this requires imperative property creation at several places.  These can be expressed in a more declarative style using <&:

function Point(x,y) {
   return tthis <& {
                  __x: x,
                  __y: y
                 };
};
Point.prototype <& {
   __validate(x,y) { return typeof x == 'number' && typeof y = 'number'}
};

function Point3D(x,y,z) {
   if (typeof z != 'number') throw "invalid";
   return Point.call(this,x,y) <& {__z : z};
};
Point3D.prototype = Point.prototype <| { //need to wire up prototype inheritance
   construtor: Point3d}   //and link prototype back to constructor
};

A similar pattern also shows up using the currently proposed Harmony class declarations:

class Point  {
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'};
     constructor(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          private __x: x;
          private __y: y;
      };
}

class Point3D extends Point  {
     constructor(x,y,z) {
          if (typeof z != 'number') throw "invalid";
          super(x,y);
          private __z: z;
      };
}

There are a couple things to note about the above.  First, a new member declaration statement-like forms (private and public) needed to be added so they could be used in the constructor body to define per instance properties. Also there is now an implicit requirement that a constructor return the same new object and never a substitute object. That is because the private/public property declarations implicitly reference the original this value passed to the constructor.  The need for the new private/public constructor declarations and the implicit this linkage could both be eliminated if the extend operator was used to set per instance properties within constructors:


class Point  {
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'};
     constructor(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          return tthis <& {
                  private __x: x,
                  private __y: y
                 };
      };
}

class Point3D extends Point  {
     constructor(x,y,z) {
          if (typeof z != 'number') throw "invalid";
          return super(x,y) <& {private __z: z};
      };
}

Note that in the above example, I assume that object literals can support "private" property declarations in a similar manner to class declaration.

Another use case for extend is adding properties to function objects.  Currently if you want to add properties to a function you have to do it imperatively like:

function Point(x,y) {
    this.__x = x;
    this.__y = y;
};

Object.defineProperty(Point, "origin", {get: function() {return new this(0,0)}});  //note this will be Point constructor

The <| allows us to declaratively add inherited properties to a function but no way to make them own properties: 

let Point = Function.prototype <| {get origin() { new this(0,0)}}  <| function (x,y) {
    this.__x = x;
    this.__y = y;
};

The extend operator would change that:

let Point = function (x,y) {
    this.__x = x;
    this.__y = y;
    }  <&  {
    get origin() { new this(0,0)}
};

Note that some might object to the need to use a let (or const) declaration rather than a function declaration in the above example.  That could be easily resolved by syntactically extending FunctionDeclaration (and similar syntactic forms) to allow the function body to be followed by <& and an object literal:

function Point (x,y) {
    this.__x = x;
    this.__y = y;
    }  <&  {
    get origin() { new this(0,0)}
};

A similar issue exists for the proposed class declarations.  That proposal includes the concept of "static" (a lot of us don't like that the term "static" in this context)  property declaration:

class Point  {
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'};
     constructor(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          return tthis <& {
                  private __x: x,
                  private __y: y
                 };
      };
     static get origin() { new this(0,0)};
}

 Static properties are really just own properties of the constructor object.  While sometimes useful, they occur relatively infrequently yet they require a additional declaration form within class bodies.  This complicates the conceptual model of a class declaration by allowing intermingling of constructor and prototype property declaration.  This also increase the potential for confusion about the meaning of "this"  (and "super") within such static property declarations.  The need for the static declaration could be eliminated by using the extend operator instead:

class Point  {
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'};
     constructor(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          return tthis <& {
                  private __x: x,
                  private __y: y
                 };
         }  <&  {
            get origin() { new this(0,0)}
        }
};

or since the value of a class declaration is its constructor:

class Point  {
     private __validate(x,y) { return typeof x == 'number' && typeof y = 'number'};
     constructor(x,y) {
          if (!this.__validate(x,y)) throw "invalid";
          return tthis <& {
                  private __x: x,
                  private __y: y
                 };
      } 
}  <&  {
      get origin() { new this(0,0)}
 };

Either or both of these alternatives could be made syntactically legal and note that the method declaration form could be applied to any method declaration in an object literal or class declaration, not just the constructor. Either alternative eliminates the need for a separate static declaration form and segregates constructor properties which should avoid confusion between them and prototype properties.


So, why use an operator like <& instead of a method named "extend":
1)  A big reason is that frameworks already use the extend name and the exact semantics we would define for it are only to match the current semantics of these frameworks.  By not using that name we avoid compatibility issues.
2) A operator form such as <& avoids issue of method redefinition and can more easily added to existing syntactic forms such as function declarations.
3) It is conceptually natural a natural companion to <|.  It makes sense to learn about the two operators together (particularly with regard to object literals on the RHS).


I think <& is a natural companion to <| and is in line with our goals to both enhancing object literals and providing a class declarations as alternative to stand-alone constructor functions.  It increases the expressiveness of object literals for declaratively defining prototypal object models and permits simplifications of the proposed class declaration form.

Allen







-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110718/44501913/attachment-0001.html>


More information about the es-discuss mailing list