The ES6 MOP (Was: New ES6 draft now available)

Allen Wirfs-Brock allen at wirfs-brock.com
Sat Nov 24 09:10:36 PST 2012


(Changing Subject to better identify  what is probably an important topic.)

On Nov 24, 2012, at 5:31 AM, David Bruant wrote:

> Hi,
> 
> I haven't been following very closely some of the most recent discussions, so I appologize if my comments have been addressed already

(The  "possible excessive proxy invariants..."  thread https://mail.mozilla.org/pipermail/es-discuss/2012-November/026510.html and a few others are quite relevant to some of the points you raise below)

Before I plunge in, I want to provide a little back ground for lurkers.

In the Rev 12 ES6 draft, section 8.1.6 and its subsections define what I consider to be the ES Meta-Object Protocol (MOP).  I don't actually use that terminology in the specification, but it is a convenient and  concise term for something important so I'm going to use it below and probably in other discussions.

The MOP generically defines the capabilities of all objects. The MOP is the abstraction layer that generic language features  see and use interact with generic objects. If a (generic) capability is not exposed via the MOP is isn't available for use in specifying language features that apply to all kinds of objects.  There may be many different forms of objects that provide alternative implementations of the MOP interface.  These alternative forms include ordinary objects and various specified exotic objects including Proxy objects, Symbol objects, Array objects, etc. Other alternative forms includes what was formerly called "host objects" and encompasses the object forms specified by W3C's WebIDL. The MOP is the semantic extension point for plugging new forms of objects into ECMAScript.

There could be many alternative MOP designs, and the current design is highly derived form the legacy ES<=5.1 internal method model.  There are various principles that might be applied to MOP design.  Two principles I believe in are: 1)  The MOP should be as narrow as possible (minimize number of MOP methods), and 2) Don't replicate common functionality across multiple MOP methods.   The current ES6 draft MOP makes compromises on these principles.  I think we can get closer it closer to them. 

> 
> Le 23/11/2012 18:48, Allen Wirfs-Brock a écrit :
>> Changes include:
>> 
>>  • Reorganized Chapter 8 into separate language type and specification type sub sections
>>  • Added Symbol property keys to the definition of the Object Type. terminology migration from talking about “property names” to “property keys”.
> I support this change. It's somewhat minor, but clearly indicates that a shift has occured.
> 
>>  • MOP changes: Added [[GetInheritance]]/[[SetInheritance]] as internal methods for accessing [[Prototype]] internal prototype chain.
> Why not [[GetPrototype]] and [[SetPrototype]]? We have a absurd number of excellent resources (including but not limited to Dmitry Soshnikov and Axel Rauschmayer's websites/blogs) which use extensively the ES5 internal [[Prototype]]. It's also been very hard to get people to understand the difference between .prototype and [[Prototype]]. I'm afraid a new change would create a lot of confusion.

[[Prototype]] is now specified as internal state of ordinary objects and only some kinds of exotic objects. (For example, symbols and Proxy objects don't have that internal state).   At the MOP level [[GetP]], [[SetP]] and [[Enumerate]] are the only methods whose ordinary implementations cares about "inheritance".  There really isn't anything in the MOP that mandates prototypal inheritance or even single inheritance.  The MOP is currently rich enough that it is even possible to define object forms that support various styles of multiple inheritance.

However, we do have legacy language and library operations for setting (__proto__) and retrieving [[Prototype]]. To define those language features they need to show up generically in the MOP. That's what [[GetInhertiance]]/[[SetInheritance]] provides.  I choose those names because I wanted to place some distance between the generic MOP level operation and the specific [[Prototype]] "internal data property" that is present in ordinary objects. BTW, "internal data property"  is a formally defined term that means private per object state.  Such state is not part of the MOP.

I think in the long run, the new names should reduce confusion, but I could be wrong. Regardless, I figured that there would be some controversy about these new names and they could easily be [[GetPrototype]]/[[SetPrototype]] but I wanted to run it up the flag pole and get reactions.  


> 
>> Added [[[IsExtensible]]/[[PreventExtensions]].
> ES5 had a note mentioning that there was no standard mechanism to set [[Extensible]] from false to true; this new way makes the note unnecessary by design of having internal methods instead of an internal property. OCaps FTW I guess :-)

The MOP only works in terms of methods.  All internal state is defined as parts of specific kinds of objects that implement the MOP methods.

> 
>> Replaced [[Get]]/[[Put]] with [[GetP]]/[[SetP]]. At some point we may be able to rename these as [[Get]]/[[Set]].
> +1 for [[Get]]/[[Set]]. That's the accessor terminology, that's what JS devs are used to. It makes the spec easier to read for them.

yes, I hope to do this but I didn't want to change too much from the current Proxy spec. in one step.

> 
>> Eliminated [[GetProperty]], [[CanPut]], [[HasProperty]], [[DefaultValue]].
> So good to see this cleanup :-)
> 
>> Added Keys, [[OwnPropertyKeys]], [[Freeze]], [[Seal]], [[IsFrozen]], [[IsSealed]].
> For both the enumerations and the layer on top of [[PreventExtension]], I'm uneasy.

So am I and this has been one of the topics in recent public and private threads. 


> * [[Enumerate]], [[Keys]] and [[OwnPropertyKeys]] are very close operations
> * So are [[PreventExtensions]]/[[Freeze]]/[[Seal]] on one side and [[IsExtensible]]/[[IsFrozen]]/[[IsSealed]]
> 
> I'm afraid that making them distinct operations increases footgun-ness. [[HasProperty]] has been removed in favored of [[HasOwnProperty]] (which might be removed in favor of only keeping [[GetOwnProperty]], though the conclusion of the discussion was the keep both IIRC) because the former could be "robustly" composed between 

Yes, I agree.  In particular I think [[Enumerate]], [[Keys]], and [[OwnPropertyKeys]] should be reduced to a single parameterized trap. Multiple traps make it harder to create an internally consistent MOP provider (eg, Proxy handler).

> 
> For proxies, [[Extensible]]ity-related operations have invariants so inconsistencies between [[PreventExtensions]], [[Freeze]], [[Seal]], [[IsExtensible]], [[IsFrozen]] and [[IsSealed]] are impossible.
> However, one can legitimately wonder what will happen if a new built-in operation using [[PreventExtensions]] is introduced in a later version of ECMAScript. Let's say experience with     unique/private symbols reveals that the current Object.freeze behavior (whatever it is in regard to symbols) is inappropriate and that adding a new Object.freezeWithDifferentSymbolHandling is introduced. The current pattern suggests that adding new [[FreezeWithDifferentSymbolHandling]]/[[IsFrozenWithDifferentSymbolHandling]] internals will be the way to go.
> I'm not sure it's a good pattern.

I think, TomV and I have tentative agreement that we could eliminate [[Freeze]]/[[Seal]]/[[IsFrozen]]//IsSealed]] from the MOP and place equivalent abstract operations into 9.3 (generic operations upon objects).  However, subsequently I've come around to what I think is better approach.  See https://mail.mozilla.org/pipermail/es-discuss/2012-November/026558.html which I quote:

Also replace [[PreventExtensions]]/[[IsExtensible]] with [[SetIntegrity]](state)//[[GetIntegrity]] where state is one of "non-extensible", "sealed", "frozen" and only lower to higher transitions are allowed. Also, for proxies this would generate a "notify only" trap.  The Object.sealed/freeze/isSealed/isFrozen/preventExtrensions/isExtensible functions could then all be implemented in terms of those two traps.

Note that this would all future addition of additional integrity states.  Other reasons are discussed in that thread.

> 
> 
> The issue seems worse in footgunness scale for enumeration operations because different operations could yield completely unrelated results. And once again, if it is wanted to add a new enumeration operation on syntax surface, it seems that a new internal operation will be necessary.

Yes I totally agree.

> 
> Here is an idea to uniformize the enumeration story while removing enumeration inconsistency footgun. I'll describe it in proxy trap terms. A unique trap (or internal operation)
> keyEnumerate: () -> iterator for {propertyKey, enumerable}
> There is this unique operation which returns an iterator not for property keys, but for an object containing a property key and a boolean signifying the enumerable boolean.
> Using this, each userland operation would use this iterator, drain it out (unless the iteration is being aborted like by throwing in a for-in loop) and filter out based on the exact userland operation so that:
> * All would filter out if propertyKey is ever a private symbol
> * Object.getOwnPropertyNames would keep all other property keys regardless of the enumerable value
> * Object.keys would filter out properties which are described as non-enumerable
> * for-in would filter out non-enumerable and retrieve the different keyEnumerate from the prototype chain objects.
> With this unique internal operation, an object is able to communicate its intent regarding what's enumerated and each enumeration userland operation only keeps what it's interested in.

Yes, something like this.  My inclination would be to add a hint parameter indicating one of the currently known variations. I think it is justified as enabling the "handler" to optimize its internal work of collecting the set of keys.

> 
> 
>>   • instanceof operator no longer uses an internal method. Instead, extensible via a @@hasInstance symbol-keyed regular method.
> I don't remember a discussion on this topic, but I think it's an interesting change. Is it for browser APIs?
> There might be an associated security worry here if any object can have its @@hasInstance changed, but one good thing is that the debate of trapping instanceof ends if it's a symbol-keyed method.

I found wiki material stating that this approach should be taken instead for further formalizing the [[HasInstance]] as part of the function part of the MOP. You can make @@hasInstance non-configurable, in situations where you care.  Just like you would need to do with the prototype property.  Note that (some/?) legacy DOM implementations apparently use a non-standard semantics of instanceof, so it seems like a necessary MOP extension point.

> 
>> • Defined all of the functions in the @Reflect module corresponding to MOP internal operations.
> IIRC __proto__ has been agreed to be an data property and there was no Reflect.setPrototypeOf operation in the harmony Reflect module proposal on purpose so that 'delete Object.prototype.__proto__' at any point would be enough to be sure that all [[Prototype]]-setting capabilities are removed.
> So I think the Reflect.setPrototypeOf  should be removed.

Do you want to be able to set __proto__ on DOM objects and other exotic objects?  If so, it needs to be part of the MOP.  "In for a penny, in for a pound". If we are going to make __proto__ part of the language than we have to accommodate just like any other feature.  If you want to be able to disable it make deletion of Reflect.setPrototypeOf be the switch.  Or define a new function for doing it.  But I don't think we should be bending the MOP to accommodate idioms like deleting __proto__..  Remember, the first point above.  [[SetInheritance]] is a generic operation, only some objects implement via a [[Prototype]] internal data property.


Allen

> 
> David
> _______________________________________________
> 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/20121124/ae32679c/attachment-0001.html>


More information about the es-discuss mailing list