[Harmony Proxies] Adding a defineProperties derived trap

# David Bruant (15 years ago)

The thread entitled "iteration order for Object" made me realize that there is no defineProperties trap.

Let's imagine I'd like to implement an object which would keep track of order in which own properties have been added/deleted to it. With the defineProperty and delete traps I can already track order of addition/deletion and reflect it on the enumerate/keys trap when required.

However, as of ES5, if I do: Object.defineProperties(proxy, props) then the defineProperty trap is called for each own property of props with an order chosen by the ES implementation (it's the same order than the one used in for..in which is implementation-dependent). Consequently, I cannot guarantee the object user that these keys will be enumerated with the order s/he provided them in props.

A defineProperties trap could allow me to throw an error saying roughly "I can't garantee my invariant with such a call, please tell me explicitely the order you want for your properties by using several Object.defineProperty".

It would obviously be a derived trap. The implementation is quite straightforward (based on ES5 Object.defineProperties algorithm 15.2.3.7) with one subtility: when retrieving all own enumerable property names of the props object, if props is itself a proxy, then the keys trap of this proxy would be called.

# David Herman (15 years ago)

We have a strawman for making the enumeration order well-specified:

http://wiki.ecmascript.org/doku.php?id=strawman:enumeration

Would that not be sufficient for the defineProperties case? I'd prefer that to adding another trap.

# David Herman (15 years ago)

[Oh sorry, I'm behind on the iteration-order mega-thread. I'll have to catch up.]

# David Bruant (15 years ago)

Le 15/03/2011 04:24, David Herman a écrit :

[Oh sorry, I'm behind on the iteration-order mega-thread. I'll have to catch up.]

Summury of how the iteration thread may affect this proposal:

If the iteration-order thread ends up to the conclusion that for objects ({}-like objects, not arrays or any other form of object) properties (any properties indifferently of being numeric or not) are ordered by order of creation/addition/deletion in ECMAScript and enumeration in for-in/keys reflects that order, then my proposal is pointless (actually not, see after) since the thing I want to implement (OrderedObjects) with is already ECMAScript objects.

On Mar 14, 2011, at 1:16 PM, David Herman wrote:

Hi David,

We have a strawman for making the enumeration order well-specified:

http://wiki.ecmascript.org/doku.php?id=strawman:enumeration

If the conclusion of the iteration-order thread is any different like this strawman (index-like properties first even for {}-like objects) or like keeping the order implementation-dependent, then my proposal of adding a defineProperties trap provides the implementor of an OrderedObject library (based on proxies) to throw on the defineProperties trap to basically say "I cannot garranty my invariant, because according to the ES spec, Object.defineProperties doesn't hold the property I am supposed to hold myself (keeping track of order)".

Would that not be sufficient for the defineProperties case? I'd prefer that to adding another trap.

In order to implement OrderedObject with proxies and keep the invariant I want them too, the enumeration strawman is actually more a pro of a new trap than a con.

Regardless of what is being specified, implementations may implement the Proxy object but keep implementation-defined enumeration order. The defineProperties trap would be a protection against these cases.

I am not 100% of a fan of adding traps easily either, however, first, I am proposing to add a derived trap (which is far less restrictive than a fundamental trap). Then, I think that this Object.defineProperties has a dependance on something the user has no control whatsoever on (enumeration order). I think that the new trap I am proposing is the best protective mecanism against this dependency.

Now that I think about it, in my particular case, the defineProperties doesn't have to throw. It could be implemented as: defineProperties: function(pdmap){ if(pdmap instanceof OrderedObject){ /* retrieve pdmap property names in order and add then with the same order through this.defineProperty */ } else throw OrderedObjectError("I cannot guaranty enumeration order if I perform this operation"); }

# Allen Wirfs-Brock (15 years ago)

I think there is something deeper lurking behind this issue. Proxies can be used to define objects whose property semantics can be quite different from those of native objects. In some situations the built-in Object. reflection functions are not going to be flexible enough to reify a mirror model of those semantics. If the designers of such proxy-based object abstraction want to expose their unique semantics to reflection-based manipulation they are is going to have define new reflection functions that are specific to their abstraction.

I think we probably need to work through some such scenarios to understand if there are any lurking issues. One thing I'm wondering about is whether any additional "back-door" access to a proxy's handler will be necessary to cross to the meta level. For example, imagine:

OrderedObject.reorder(obj,["prop3","prop2","7","prop12","1"]);  //2nd argument re property names in their new order

How does the the reorder function communicate with obj's handler instance without also exposing a meta-level API to the application-level. Actually, private names might provide a solution.

# David Bruant (15 years ago)

Le 15/03/2011 18:07, Allen Wirfs-Brock a écrit :

I think there is something deeper lurking behind this issue. Proxies can be used to define objects whose property semantics can be quite different from those of native objects. In some situations the built-in Object. reflection functions are not going to be flexible enough to reify a mirror model of those semantics. If the designers of such proxy-based object abstraction want to expose their unique semantics to reflection-based manipulation they are is going to have define new reflection functions that are specific to their abstraction.

I fully agree.. In the same vein, I was recently thinking about the non-ES5 traps (getPropertyNames/getPropertyDescriptor). The equivalent Object reflection functions can be easily implemented for native objects. However, from a proxy point of view, a native or user-defined Object.getPropertyNames makes a big difference since the former triggers the getPropertyNames trap while the latter cannot (and calls the traps used in the user-defined implementation instead).

I think we probably need to work through some such scenarios to understand if there are any lurking issues. One thing I'm wondering about is whether any additional "back-door" access to a proxy's handler will be necessary to cross to the meta level. For example, imagine:

OrderedObject.reorder(obj,["prop3","prop2","7","prop12","1"]);  //2nd argument re property names in their new order

Exactly. I thought about that soon after posting my message too. Yesterday, I've been working on a prototype in preparation of a discussion on the proxy proposal open issue (about normalizing/throwing/doing nothing if traps input/output aren't valid). While working on it, it has raised some issues with the idea of a "proxy factory". The baseline of the "problem" is that if you want to create an OrderedObject, you cannot go for: function OrderedObject(){} var oo = new OrderedObject(); It basically comes from ES5.1 13.2.2 [[Construct]] step 1 : "Let obj be a newly created native ECMAScript object". So if I want to create a Proxy-based object, my constructor itself has to be a function proxy (to reify [[Construct]]). So I don't know, there might be things to investigate in order to discuss factory patterns for proxies. I'll do that in another thread after releasing my code, it'll be a way to discuss on something concrete. Discussing factory patterns may be related to the idea of how to deal with providing other abstraction-specific reflection functions.

How does the the reorder function communicate with obj's handler instance without also exposing a meta-level API to the application-level. Actually, private names might provide a solution.

I am not familiar yet with all other Harmony proposals but private names might be a solution to the problem you're pointing and modules might offer an opportunity to help out with factory patterns.