time to trim mustache

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Jun 5 17:13:02 PDT 2012


On Jun 5, 2012, at 1:30 PM, Irakli Gozalishvili wrote:

> I just want to (re)express my (and many others) concern about new syntax.  While Object.extend adds useful feature (Although I think Object.define would be more appropriate name) I don't think new syntax is really necessary. I do think that new syntax needs a lot more justification then new semantics. I also would argue that it's a good idea to alway make syntax changes in a separate iteration from the one where associated semantics have being introduced. That would allow both community and come tee to see these semantics in practice and having more knowledge to decide what syntax sugar would work best, if any new syntax even will turn out to be necessary.
>  
>> One of these things is installing private named properties upon an existing object.  As currently specified, those could not be communicated to an extend function via an object literal because we have disallowed any form of refection upon private named properties. Object.extend could not see them on the literal object in order to copy them.  Trying to solve this problem by saying that Object.extend has special reflection privileges would violate the encapsulation that the non-relection on private name properties was intended to provided.  
> 
> But many other variations of this would do the job as well without a new syntax:
> 
> Object.extend(target, privates(
>    name1, value1,
>    name2, value2
> ))
> 

I'm not exactly sure what you privates function returns (which is the actual value that is passed to extend) but it can't be an object where name1 and name 2 are property names because the current rules of private name enumerations would prevent them from being enumerated. Also I'm assuming that in a typical case you will want to install a related set of public and private methods on an object.

I guess you might express this as:

const private1 = new Name;
const private2 = new Name;
const private3 = new Name;
Object.extend(target, {
      publicMethod1() {...this[private3]...},
      publicMethod2() {...this[private2]()...},
      publicDate1: 42,
      get publicGet () {return this[private1]}
});
Object.extend(target, privates(  //assume private returns a object kind known to Object.extend that is capable of iterating over the "name" values it was constructed with
   private1,  undefined,  //private1 need to be a data valued property, not a method .  Let's assume that any non-function value is an indication of that intent
   private2, function(x) {...this[private1]...},  //note that traditional function declaration must be used. => doen' t have dynamic this and  concise methods are not available here
   private3, accessor({get: function() {return this[private2](this[private1])}})   //accessor produces some sort of descriptors that identifies the need to add an accessor property
});

more likely you would want to get it all into a single extend call, perhaps by making the privates be an additional argument or perhaps you intended this version of extends to support multiple arguments.  In either case you would have 

const private1 = new Name;
const private2 = new Name;
const private3 = new Name;
Object.extend(target, {
      publicMethod1() {...this[private3]...},
      publicMethod2() {...this[private2]()...},
      publicDate1: 42,
      get publicGet () {return this[private1]}
      },  
      privates(  //assume private returns a object of kind known to Object.extend that is capable of enumerating the "name" values it was constructed with
           private1,  undefined,  //private1 needs to be a data valued property, not a method .  Let's assume that any non-function value is an indication of that intent
           private2, function(x) {...this[private1]...},  //note that traditional function declaration must be used. => doen' t have dynamic this and  concise methods are not available here
           private3, accessor({get: function() {return this[private2](this[private1])}})   //accessor produces some sort of descriptor that identifies the need to add an accessor property
      )
);

In either case, once you have to start defining helper functions that generate argument descriptors like above, I don't see a lot of advantage over using Object.defineProperty/defineProperties:

const private1 = new Name;
const private2 = new Name;
const private3 = new Name;
Object.defineProperties(target, {
      publicMethod1: {value: function() {...this[private3]...}, configurable: true},
      publicMethod2: {value: function() {...this[private2]()...}, configurable: true}
      publicDate1: {value:42, enumerable: true, writable: true, configurable: true},
      publicGet : {get: function () {return this[private1]}, enumerable: true, writable: true, configurable: true}
      };
//private name properties probably can't use defineProperties because of the private name reflection probation 
Object.defineProperty(target, private1,   {value:undefined, enumerable: true, writable: true, configurable: true});
Object.defineProperty(target, private2,   {value: function(x) {...this[private1]...}, configurable: true}) ;
Object.defineProperty(target, private3,   {get: function() {return this[private2](this[private1])}, configurable: true} );
      )
);

All of the above look pretty ugly. Just to contrast, here is how the same might be expressed using mustache:

const private1 = new Name;   //private private1, private2, private3; would be sweeter
const private2 = new Name;
const private3 = new Name;
target.{
     publicMethod1() {...this[private3]...},
     publicMethod2() {...this[private2]()...},
     publicDate1: 42,
     get publicGet () {return this[private1]},
    @private1:  undefined, 
    @private2(x) {...this[private1]...},  
    get @private3() {return this[private2](this[private1])}
};

(BTW, I'm using the @pname syntax because I don't believe we are going to get away without having a way to expression private named property definitions in object literals and class definitions.)

I guess I don't agree that the non-syntactic form is doing the above just as well.  If the contents of the mustache could be passed as an object literal to extend then we would be moving in the right direction.  However, we would have to first deal with the private name issues and super rebinding (which I didn't include in this example)

>> 
>> Anther ES6 specific semantic that has always been part of the mustache proposal is the correct binding of methods with super references. I've described this many times. So I'll just describe it again other than to reinforce that mustache is the great way to dynamically associate super referencing method to an object without running into the pitfalls that arise with the defineMethod alternative. I see how with mustache we can live without defineMethld.
> 
> But omitting reflection APIs is pretty dangerous path to go with IMO. JS has being great as it was always possible to fix things that were not working for you. I have feeling that providing semantics only through new syntax may take away this great power of JS.

Yes, I agree. One of the principle's I've been trying to follow up to this point is that there needs to be a procedural way to do any sort of object construction that can be done syntacticly. But where we are as of the last TC39 meeting is that we don't have either way to add a super bound method to an already instantiated object.  Object.defineMethod was cut and mustache isn't in.  I think mustache is compelling enough that I would probably compromise my principles and choose it over defineMethod if we only were allow one or the other. However, I really believe we need a reflective way  to do super binding.

Having thought about this a bit since the meeting, I currently think there is probably a way to have such reflective super binding without creating the footgun concerns that people had with Object.defineMethod.  The way to do it may be to put the functionality in the reflect module:

reflect.rebindSuper(function, object)   //create a new function just like the first argument but with super bound to object
reflect.hasSuper(function)  //test if argument uses a super binding
reflect.isSuperBound(function,object)  //test if the function's super binding is object

I think living there they would be much less of an attractive nuisance and would provide the necessary capability to do things like enable an Object.extend like function that would do the right things when encountering functions that reference super


Allen









> 
> Regards
> --
> Irakli Gozalishvili
> Web: http://www.jeditoolkit.com/
> 
> On Monday, 2012-06-04 at 09:45 , Brendan Eich wrote:
> 
>> Kevin Smith wrote:
>>> Thanks Dave,
>>> 
>>> Of the 3 use cases you mentioned, I think unique names are probably
>>> sufficient for 1 and 3. For the second use case (an inaccessible
>>> piece of data associated with an object), would not a weak map also be
>>> appropriate?
>> 
>> No, WeakMaps have two problems we've covered in this list:
>> 
>> 1. Less efficient than private names.
>> 
>> This matters when you can least afford it, and it matters for private
>> names used to program in the large using objects in JS. WeakMaps require
>> special GC handling and they're an extra object with internal mutable
>> state. Private name objects are flat, frozen, and can be optimized a lot
>> harder.
>> 
>> 2. You cannot abstract property access:
>> 
>> function get(obj, prop) { return obj[prop]; }
>> 
>> works with a private name object referenced by prop. No such abstraction
>> can be done with a weak map.
>> 
>> /be
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
> 
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120605/3439ce31/attachment.html>


More information about the es-discuss mailing list