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

Dmitry A. Soshnikov dmitry.soshnikov at gmail.com
Mon Jul 18 11:26:05 PDT 2011


On 18.07.2011 21:32, Allen Wirfs-Brock 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.

JFTR: object.extend(...) first appeared in Prototype.js and was borrowed 
there from Ruby. In Ruby though, this method method is delegation-based 
(that is, a hidden class is created which is inserted as a direct 
ancestor of the object and the superclass of the hidden class is set to 
extending module). That is, if something changes in the mixed module, 
the changes are reflected on the object which extended it.

module Foo
   def bar
     print "Foo:bar"
   end
end

obj = {}
obj.extend(Foo)

obj.bar() # Foo:bar

# change the module's method
module Foo
   def bar
     print "New Foo:bar"
   end
end

# found by delegation
obj.bar() # New Foo:bar

Perhaps ES wants also this approach. OTOH, "extend" practice is already 
strongly used in JS and from this viewpoint it makes sense to make 
extending with copying and not using delegation.

Below I add only small note since examples are similar and personally 
for me cause ambiguous perception.

>  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) {
>

Am I alone here who want to put a space after the methods in this 
proposal? ;) It's like operators, really:

if(x==10) // :(

if (x == 10) // :)

new(x,y) // Arrgghh!

new (x, y) // :)

Since these are method names, I understand that we shouldn't put a space 
after them (as we do in case of functions with names), but just visually 
disturbs me :)

>               if (!this.__validate(x,y)) throw "invalid";
>               return this <| {
>                       __x: x,
>                       __y: y
>                      }
>           };
>     }
>
>

What's the reason we want back to desugared factories (with all this 
explicit stuff, manual `return`, setting manually proto, etc) instead of 
improving constructors syntax by forming them to classes? Is the syntax 
already accepted for classes by the way? I remind again this syntactic 
form which seems (for me?) more elegant 
http://dmitrysoshnikov.com/scheme-on-coffee/class.html.

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

I'm really sorry, perhaps it's only for me, but all these examples seem 
to me too cryptic in respect of syntax (even like Perl). All these `} <& 
{` seem for me as a syntactic noise. I even better would like to see 
some longer but humanized "extends" keyword. Maybe it's about the habit 
and perhaps later it will become for me not so cryptic (after all I can 
accept -> #, {||}, etc. as an alternative for `function`).

E.g.

let foo = {x: 10}

// delegation-based extending
let bar = foo extend {
   y: 20;
};

// copy-based extending
bar.mixin({
   z: 30
});

Pity btw, that < is already borrowed and is a normal operator, we could 
use it instead of <| which is not the best on different fonts. 
<bikesheding value="Mayber a back arrow? <- " />


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

All examples seem syntactically for me again cryptic in comparing 
classes syntax e.g. in Ruby, Coffee or Python (maybe these __underscores 
make the code more cryptic though?).

> 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.
>

Yes, all reasons are true, I just want the syntax be less "Perlish".

Dmitry.

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


More information about the es-discuss mailing list