On Jul 18, 2011, at 11:29 AM, Sean Eagan wrote: > 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.
Yes, I should have mentioned at least the array case. > > Also, how about |> as opposed to <&, since it is a dual to <| adding > own rather than inherited properties? I'd be a bit concerned about some people getting confused about which direction of the "arrow" corresponds to each operation. Might be particularly hard for people with dyslexia. I have played around with some other possibilities, for example, +<| . I like the nemonic value of having + or & as part of the operator symbol for "extend" but unfortunately <+ can't be used. Allen > > 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