On Dec 21, 2012, at 7:01 AM, Tom Van Cutsem wrote:
> 2012/12/21 Allen Wirfs-Brock <[email protected]>
>
> On Dec 20, 2012, at 12:07 PM, Tom Van Cutsem wrote:
>
>> 2012/12/20 Allen Wirfs-Brock <[email protected]>
>>
>> On Dec 20, 2012, at 2:21 AM, Tom Van Cutsem wrote:
>>> If target.baz is a writable data property
>>> proxy.baz = 42 will eventually call Object.defineProperty(proxy, 'baz',
>>> {value:42})
>>> (and *not* Object.defineProperty(proxy, 'baz',
>>> {value:42,enumerable:true,writable:true,configurable:true}) as it did
>>> previously)
>>> This behavior is consistent with the method and accessor case: in all
>>> cases, the target delegates back to the proxy.
>>
>> This is actually essential to maintaining ES5 default semantics because
>> {value:42} and {value: 42, enumerable:true,writable:true,configurable:true}
>> have quite different meanings to the ordinary [[DefineOwnProperty]].
>>
>> Indeed. And this is what caused the issue I reported in the OP.
>>
>>
>> It is equally important that if target.baz does not exist that
>> [[DefineOwnProperty]] is called on proxy with with the full {value: 42,
>> enumerable:true,writable:true,configurable:true} descriptor to ensure that
>> the new property gets created using the "crated by assignment" attributes
>> rather than the [[DefineOwnProperty]] defaults.
>>
>> Yes, this is the case.
>>> One thing that I learned from all this is that it's simpler to think of the
>>> proxy as *delegating* (as opposed to forwarding) to its target by default.
>>> And under the semantics of invoke = get + apply, that is actually the
>>> simplest option.
>>
>> Yes, this is the best way I have found to think about them and I've been
>> patiently waiting for you to see the light :-) However, a corollary is
>> that most internal method calls within derived internal methods/traps need
>> to delegate back to the original "receiver". In some cases, we don't
>> provide the original receiver as an extra argument, so it isn't available to
>> do this.
>>
>> A quick scan of the ordinary internal method suggests that we may have this
>> problem for [[Delete]], [[Enumerate]], [[Keys]], and [[GetOwnProertyKeys]].
>>
>> More generally, I would argue that all Proxy traps (and the corresponding
>> Reflect functions) potentially need access to the original "receiver". We
>> don't know what somebody is going to do in such traps and they well need to
>> call back to the original receiver for the same sort of consistency issues
>> we have encountered with [[SetP]]. this is particularly apparent if you
>> think multiple levels of proxies chained through their target slots.
>>
>> I'm not sure I follow. In my understanding, the original Receiver is only
>> needed for traps that involve prototype-chain walking and are thus
>> |this|-sensitive. That would be just [[GetP]] and [[SetP]]. One can make the
>> case (David has done so in the past) for [[HasProperty]] and [[Enumerate]]
>> since they also walk the proto-chain, although it's not strictly necessary
>> as the language currently does not make these operations |this|-sensitive.
>
> The proxy target delegation chain is also this-sensitive when it invokes
> internal methods. For example, in the revised [[SetP]] step 5 it is
> important that the [[DefineOwnProperty]] calls (in 5.e..ii and indirectly in
> 5.f.i are made on Receiver and not O.
>
> Let's take a simple case, the [[Keys]] internal method. It needs to do a
> [[GetOwnProperty]] call for each property to determine whether or not it is
> enumerable. If [[Keys]] is invoked on a Proxy and automatically delegated to
> the target, should the [[GetOwnProperty]] call be made to the target or to
> the original receiver (the proxy)? If it is invoked on the target, we will
> get a list of what the target thinks are its enumerable properties. The
> proxy, itself, might have a different idea of which of its properties are
> enumerable. Since the original operation was invoked on the proxy, we
> presumably want [[Keys]] to tells us what that object (the proxy) thinks are
> its enumerable own properties not what the target actually has as enumerable
> own properties. The latter could actually be leaking a secret that the
> proxy is trying to keep.
>
> If we're talking secret-keeping-proxies, these probably should just override
> and implement all traps, and not default to forwarding to the original
> target. That feels too brittle for an abstraction that's trying to keep a
> secret.
It's just an example of a very simply use of Proxy's where a ES programmer
might naively think they only need to over-ride a single fundamental trap in
order to get their desired effect. For example, they might want a property
named "length" to always be treated as non-enumerable. They might reasonably
think that all they need to do is a handler that looks like:
{getOwnPropertyDescrptor(target,key) {
let desc = Reflect.getOwnPropertyDescriptor(target,key);
if (desc && name==="length) desc.enumerable=false;
return desc;
}}
>
> Here is another way to think about it. Both the ES prototype chain and the
> ES proxy target chain can be view as examples of Lieberman style delegation,
> but at different abstraction levels. Prototype delegation is about
> delegation of ES functions. The self-calls take place at the level of ES
> functions and the implementation layer is careful to pass the correct this
> values to inherited (ie delegated) functions. The mechanisms for describing
> the semantics (and perhaps implementing it) uses the ES internal methods.
> But, for prototype delegation purposes, the internal methods do not make true
> self-calls. Historically, ES internal methods are not inherited/delegated
> along the prototype chain. This is why the Receiver needs to be passed as
> an explicit parameter to the internal methods that are responsible for
> implementing ES function level self-call semantics.
>
> For proxies, things are flipped. The delegation isn't at the level of ES
> functions, instead it is at the level of internal methods. The self-calls
> that are of interest are not to ES level methods but to internal methods.
> That's why [[Keys]] really should be doing a self-call of [[GetOwnProperty]]
> through the original receiver rather than to the same target object that
> fielded the [[Keys]] call. Similar situations exist within [[Delete]].
>
> Thanks. That's a nice explanation of the two levels of delegation.
>
> If you step back a bit and just think about the concepts of Lieberman
> delegation and self-calls without worry about the specific of the proxies or
> the ES MOP I think you will come to see that delegated target calls naturally
> should self-call back to the original object. That's what Lieberman style
> delegation is all about.
>
> Or, here's another way to look at it. You need real self-calls that go back
> to the original receiver whenever you have an interface with interdependent
> operations (some of which may be "derived" and other may be "fundamental")
> and where you permit individual operations to be selectively delegated.
> Otherwise, leaf objects won't present a "self"-consistent set of operations.
> This problem goes away, if you do not allow delegation at the individual
> operation level, but instead only permit either all operations or no
> operations of the interface to be over-ridden/delegated. This "no individual
> over-rides" solution is essentially what the ES spec. historically applied to
> internal methods. But direct proxies now allow over-rides at the individual
> internal method granularity. This exposes the sorts of inconsistencies I'm
> describing.
>
> Your references to "derived" vs "fundamental" and self-consistency make me
> think about the Handler API again:
(yes, that was intentional)
>
> If a proxy handler simply subclasses Handler, does not override any derived
> trap, and overrides all fundamental traps to simply forward to the target,
> then I think you basically achieve what you would have achieved if the ES6
> spec used delegation internally.
>
> If a derived trap is called on such a handler, it inherits the default
> implementation from Handler which basically mimics all the ES6 built-in
> algorithms, but with explicit self-sends to call the fundamental traps.
> Granted, the fundamental traps themselves are still forwarded (not
> delegated), but for fundamental traps, there is no other derived operation to
> call back on anyway (they are not Receiver-sensitive, so to speak), so that's
> OK.
>
> As Andreas points out, modifying the built-in semantics of normal objects to
> support delegation is a non-trivial change that may impact the performance of
> normal, non-proxy objects. At the very least implementations will want to
> branch on whether the target and the receiver object are the same (then they
> can fall back on the current implementation), otherwise they must explicitly
> execute the ES6 spec algorithm.
>
> I think that with the provision of the Handler API, we provide Proxy authors
> with the same benefits than if we would add delegation to the ES6 built-ins,
> without any required changes to existing internal methods for normal Objects.
Yes, I generally agree that Handler is a way out of this. But if that is our
solution to the problem then I still think we have a usability issue with the
current Proxy API design.
Allen
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss