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

Sean Eagan seaneagan1 at gmail.com
Mon Jul 18 11:29:11 PDT 2011


It would also allow declarative non-integer own properties for arrays,
and arbitrary own properties to regular expressions, numbers,
booleans, and strings, though I can't think of any specific use cases
for those off of the top of my head.

Also, how about |> as opposed to <&, since it is a dual to <| adding
own rather than inherited properties?

On Mon, Jul 18, 2011 at 12:32 PM, Allen Wirfs-Brock
<allen at wirfs-brock.com> wrote:
> 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
>
>
>
>
>
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>



-- 
Sean Eagan


More information about the es-discuss mailing list