[[Invoke]] and implicit method calls

Allen Wirfs-Brock allen at wirfs-brock.com
Wed Sep 11 13:05:27 PDT 2013

On Sep 11, 2013, at 11:02 AM, Brendan Eich wrote:

>> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
>> September 11, 2013 7:08 AM
>> 1) The [[Invoke]] operation is meant to primarily trap method calls generated by user-code of the form |obj.m(...args)|. This is by far the most common case, and I believe we should not extend [[Invoke]] with additional arguments that are irrelevant to this primary use case. Rather than having a boolean argument, better to statically separate the cases into two separate internal methods.
> +a bazillion. Srsly!

I'm not sold.  Two internal methods means there must be two proxy traps that must always be implemented as a pair.  It would simply be wrong for a handler to implement the invoke trap and not also implement  the invokeConditional trap.  And inheriting from DelegatingHandler or ForwardHandler doesn't help because if either invoke or invokeConditional is over-ridden then then other also needs to be over-ridden.

Having to implement two coordinated traps, in what is probably going to be a  relatively rate use case (needing to over-ride [[Invoke]]), sounds to me like a probable footgun.  On the other-hand, having a single trap with an additional argument more explicitly communicates that a handler needs to deal with both the conditional and unconditional situations.

obj.m(...arg) is clearly the (dynamically) dominant use case for [[Invoke]]. But, besides it,  there are actually statically more places in the ES6 spec where [[InvokeConditional]] would be appropriate than where [[Invoke]] is appropriate (6 to 3).  And that isn't counting the implementation to the MOP calls on Proxy objects which would also be appropriate for [[InvokeConditional]] (but more on that below).

The dominant ordinary object use case for [Invoke]] is going to inlined and optimized by implementations so an optional extra argument that isn't used in by obj.m(...arg) will make absolutely no difference.  Neither is it likely to make a difference in the built-ins that need to use conditional invokes.  For unconditional [[Invoke]] on proxy objects the conditional argument basically adds only a single (not taken) conditional. 

The real cost seems to be the authoring experience.  Two coordinated traps is going to be harder to author and more error prone than a single trap with a parameter.

>> 2) There are a number of places in the spec where [[Invoke]] should be called conditionally, if we know for sure the property is callable.
>> Currently the pattern for this is [[Get]]+[[Call]]. We cannot refactor to [[Has]] + [[Invoke]] in general, because [[Has]] will return true also for non-callable values.
> This is the toString/valueOf legacy.

There are currently 6 such places (not counting the Proxy trap invocators):
3 in ToPrimitive (ie toSrting/valueOf)
1 in instanceof to access @@hasInstance (legacy conpat. for missing @@hasInstance)
1 in [ ].toString to conditionally invoke 'join' method
1 in JSON.stringify conditionally invoke 'toJSON'

>> If we believe these are call-sites where it is worth avoiding the allocation of a function, then having an additional internal method like [[GetMethod]] or [[InvokeConditional]] makes sense, but I doubt it's worth the added complexity.
> We have zero evidence based on legacy (i.e. what's deployed).

The consistency we are looking for is that all method invocations through a proxy go through an invoke trap so the handler can enforce consistent method invocation semantics. 

I think the simplest solution is the extra parameter on invoke.  If I can't sell that, my next preference is [[InvokeFunction]] as I described in an earlier message:

I beginning to think that the best solution is to add a [[InvokeFunction]] internal method/trap.  It's just like [[Invoke]] except that it assumes that the [[Get]] step has already been performed (the function is passed in rather than the property key).  This still allows the handler to intercede in mapping the this value or other arguments.  In fact, perhaps, we could simply replace [[Invoke]] with [[InvokeFunction]] and turn all the current [[Invoke]] calls into [[Get]]+[[InvokeFunction]].  This would also address the conditional [[Invoke]] issues being discussed in this thread.

[[InvokeFunction]] requires use of [[Get]] to access a method so it reintroduces the possibility that a handler might have to cons up a function. I suspect this is rare which is the conclusion we came to when [[Invoke]] was originally considered long ago. 

>> 3) For proxy trap invocations I maintain we are still better off with [[Get]] + [[Call]] to keep double-lifting as simple as possible.
> Sorry if I missed it: what complicates things if we use [[Invoke]] to support double-lifting?

however proxy trap invocation should really be [[Invoke]] for semantics consistency.  It really is a method invocation and the meta-meta-level handler should be given the opportunity to apply any method invocation policy it may have. Either a conditional [[Invoke]] or [[Get]]+[[InvokeFunction]] would supply adequate semantics. 

Either some form of conditional [[Invoke]] or a [[InvokeFunction]] trap would still support double-lifting via a single trap.  In the case of conditional [[Invoke]], it is that trap that would be implemented at the the meta-meta-level. With [[InvokeFunction]] it is the [[Get]] trap that would be implemented.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130911/bd86250e/attachment.html>

More information about the es-discuss mailing list