[[Invoke]] and implicit method calls

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

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

> On Sep 21, 2013, at 2:51 AM, Tom Van Cutsem wrote:
> 2013/9/20 Allen Wirfs-Brock <allen at wirfs-brock.com>
>> BTW, I would want to use  [[InvokeFunction]] for both directly
>> obj.method() and in the internals of F.p.call/apply
> As I mentioned to your reply at the time, I believe the latter would break
> the expectations of existing code.
> There is an even stronger expectation among existing code that
>    obj.m()
> is equivalent to
>    var f=obj.m; f.call(obj)
> with the possibility of other computation being inserted between
> initializing f and calling it.
> More concretely, existing code expects that
>     obj.valueOf()
> is equivalent to
>     obj.valueOf.call()
> but if obj is a transparent forwarding proxy on a map instance (or most
> other built-ins) the first form will work as expected and the second form
> will throw unless something like [[InvokeFunction]] is used within F.p.call.

Or if obj is a transparent forwarding proxy done right, i.e. whose "get"
trap returns a wrapper function that re-binds |this| and then forwards (a
membrane does this automatically).

> The re-specification of call/apply in terms of [[InvokeFunction]] would be
> semantically identical to the current specification in all cases where the
> passed this value is not a Proxy. (or  some other exotic object that
> redefined [[InvokeFuntion]])
> Using [[InvokeFunction]] within F.p.call/apply would still retain their
> existing behavior for all normal situations where the this value is not a
> Proxy, so there would be no observable difference for existing code that is
> not designed to deal with proxies. Plus that existing code would continue
> to observe the obj.m() :: obj.m.call(m) equivalnce even when obj is a Proxy.
> Code that uses F.p.call/apply to apply a function it acquired through
> manual lookup typically expects it's going to execute the original
> behavior, and nothing else:
> var original_push = Array.prototype.push;
> ...
> original_push.call(unknownObject, ...args); // programmer expects this to
> either do the specced push() behavior or throw a TypeError (assuming
> original definition of 'call')
> Using this specific example, you are saying the programmer expects the
> above to throw if unknownObject is a transparently forwarding proxy over an
> Array instance.  I doubt if that is the actual expectation.  I think the
> expectation is that the orginal_push will be invoked as if was the function
> retrieved via unknownObject.push.   In other words, it is not trying to
> change the normal this binding semantics of obj.push(), it is only trying
> to force a local bind of obj.push to a specific value (ie, circumvent
> obj.[[Get]]("push")).

Given that Array.isArray(proxyForArray) is specced to return false, I don't
see why the above code should work transparently on proxies-for-arrays,
while a simple Array.isArray test would fail.

> I'm skeptical that there are many (any?) existing situations where
> F.o.call/apply is used with the failure expectation WRT transparent proxies
> that is implicit in your example. Do you know of any?
> For situations that really want to do a naked [[Call]] without any
> proxy-based intercession the way to do it would be
> Reflect.call(original_push,unknownObject,...args) rather than
> original_push.call.(unknownObject,...args).

Indeed, except that I expressed concerns about the expectations of existing
ES5 code, for which it is too late to opt-in to the new Reflect.apply

> JS fundamentally decouples property lookup from method call and thus has
> the ability to express non-polymorphic function calls. We shouldn't
> virtualize [[Call]].
> Are you suggesting that we should not have a Proxy "apply" trap?

No, not at all. For proxies that represent/wrap functions, it's necessary
to virtualize [[Call]].

My argument is that we should leave the [[Call]] behavior of *ordinary
functions* alone.

Re-specifying Function.prototype.{apply,call} in terms of a new
[[InvokeFunction]] MOP would mean that proxies can intercept the call
behavior of ordinary functions.

> But, more to your point, the existence of the this argument to call/apply
> means that they are inherently polymorphic calls in the sense you are
> talking about.  If the author of a function doesn't want it to be
> polymorphic on its this value then they should not write any references to
> |this|.
> If a proxy wants to intercept method calls, it can return a wrapper
> function from its "get" trap and override "invoke". I'm pretty sure
> virtualizing [[Call]] will be a bridge too far.
> If this is the solution, then ForwardingHandler should do exactly that
> within its default "get" trap.  However, that is going to also be wrong
> some of the time.  You can't generically say that all [[Get]]'s that return
> a function value can replace that value with some other function and not
> expect to break something.

Absolutely. Proxies enable many different possible wrappers. There is no
"one true way" of building wrappers, so obviously the default behavior will
be wrong on some occasions.

I'm willing to consider changing the behavior of ForwardingHandler.get to
auto-wrap functions and re-bind their this-value. I think you may be right
that for users subclassing ForwardingHandler, they probably want the
this-rebinding to happen for them by default.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130923/791c974c/attachment.html>

More information about the es-discuss mailing list