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 <al...@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@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -- Sean Eagan _______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss