[[Invoke]] and implicit method calls

Tom Van Cutsem tomvc.be at gmail.com
Mon Sep 23 01:33:27 PDT 2013


2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>

>
> On Sep 21, 2013, at 2:53 AM, Tom Van Cutsem wrote:
>
>
> 2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>
>
>>
>> On Sep 20, 2013, at 5:31 PM, Brendan Eich wrote:
>>  > Given this, having the legacy internal calls continue to use get+call
>> seems fine to me. A proxy implementing toString, e.g., can make it work
>> using these traps just as well as via get+invoke, and without double lookup
>> or boolean-trap-smelling (id | func) parameterization of invoke.
>>
>> In that case,  why not just use [[Get]]+[[InvokeFunction]] in all cases
>> (including obj.m(()) and not have the current [[Invoke]] at all.  It allows
>> the proxy handler to correctly intercede on all method invocations
>> including conditional cones.
>>
>
> Yes, except:
>
> a) proxies must then still allocate a temporary function object in the get
> trap. Thus, the primary reason for having a derived trap (less
> allocations), disappears.
>
>
> ??? Not in the normal cases where function already exist for the method
> properties.  They would only have to be allocated for the more exceptional
> situation where the handler is trying to directly implemented method
> behavior within the handler (via a switch statement, etc)
>
> or was there something else you had in mind with the above assertion?
>

I was thinking primarily about virtual object use-cases, where the method
properties don't necessarily already exist as functions.

But also membranes, for instance. A membrane must make sure no direct
reference to the method leaks to the caller, and thus must return a wrapper
from the "get" trap regardless of whether we include "invokeFunction".


>
> b) every method call on a proxy triggers at least 2 traps ("get" +
> "invoke"). Another selling point of invoke() was that method calls go
> through one trap only.
>
>
> Yes, we do have the added overhead, of two traps but with the benefit is
> that we get a more consistent semantics that is a better match to current
> ES expectations.
>
> If we really are worried about the overhead of two traps we could have a
> derived "invoke" trap that does "get"+"invokeFunction" but I'm not sure the
> complexity is worth it (or that there would actually be any saving, isn't
> the the default implementation of "invoke" just going to essentially
> trigger the other two traps?)
>
> c) double-lifting needs 2 traps (get + invoke) rather than just get.
>
>
> I don't think so, using [[invokeFunction]] instead of [[Invoke]]
> eliminates the need for two:
>
> the current dispatch mechanism for Proxy Mop operations is essentially
>     let trap = handler.[[Get]]("handlerName");
>     ...
>     trap.[[Call]](handler,args);
>
> if [[InvokeFunction]] was available this would become
>
>     let trap = handler.[[Get]]("handlerName");
>     ...
>     handler.[[InvokeFunction]](trap, handler,args);
>
> The default behavior of [[InvokeFunction]] on a Proxy that does not have a
> corresponding "invokeFunction" trap defined is equivalent to a [[Call]] of
> the passed function so the behavior would be exactly the same.  All the
> metameta handler needs to define is "get".
>

Ok, I stand corrected. But that default behavior surprised me. The default
behavior of all other traps is to forward the intercepted operation to the
target. The way you describe things, this would not be true of
"invokeFunction".


>
> At that point, we're better off dropping [[InvokeFunction]] entirely.
>
> As Brendan mentioned, methods are extractable in JS. A new MOP operation
> doesn't relieve proxies from honoring that contract.
>
>
> It still appears to me that [[Get]]+[[InvokeFunction]] and respec'ing
> F.p.call/apply in terms of [[InvokeFunction]] is the closest semantic match
> to this expected behavior plus has the least anomalies WRT transparent
> proxying of built-ins with private state (and user defined classes that we
> WeakMaps for private state).
>

Except that respec'ing F.p.call/apply changes the [[Call]] behavior of
existing non-proxy functions. Am I the only one to think this is
potentially a serious hazard?


>
> I believe we made the right trade-offs so far and still stand by the
> status-quo.
>
>
> Which status-quo?  We have some fundamental semantic consistency issues
> involving Proxies and method invocation.
>
> We have a number of alternatives that have been discussed and they all
> seem to have both pros and cons associated with them. I also believe that
> done of them perfectly resolves all of the semantic consistency issues and
> at the same time preserves perfect actual or conceptual backwards
> compatibility.  Personally, I'm finding it difficult to remember all of the
> various pros and cons that we have individually discussed for each
> alternative.  I suspect we need to put together a side-by-side for each
> alternative so we can compare them.
>

Indeed, there are trade-offs and there is no silver bullet.

The status-quo, which I advocated, entails:

- we keep the invoke() trap, with its current signature: invoke(target,
propertyName, argsArray, receiver)
- conditional method calls in the spec are still expressed as [[Get]] +
[[Call]], following the common JS idiom

After your comments, I would add:
- we change ForwardingHandler.get to automatically re-bind |this| by
wrapping function-valued data properties of its target

The only drawback of the status-quo, that I see, is that proxies that do
not subclass ForwardingHandler must exercise more care in their "get" trap
if they want to intercept the this-binding. Most other alternatives make it
easier for proxy authors to avoid this issue, but at the expense of making
the JS MOP strictly more complex/less intuitive.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130923/84d51270/attachment-0001.html>


More information about the es-discuss mailing list