[[Invoke]] and implicit method calls

Brendan Eich brendan at mozilla.com
Thu Sep 19 06:15:47 PDT 2013


What if we made invoke take either an identifier to call, or a got 
function to call? Then get+invoke would pass the "got" function.

/be
> Allen Wirfs-Brock <mailto:allen at wirfs-brock.com>
> September 19, 2013 1:39 PM
>
> Seems very unlikely. The @@hasInstance access is new so it isn't a 
> backwards compat issue. BTW, these are static counts. For example, a 
> typical call to ToPrimitive will only make a single such conditional 
> invoke.
>
> Arguably allowing a String or Number wrapper to be transparently 
> proxied and hence work like a unproxied wrapper WRT ToPrimitive is one 
> the reasons we need to do this.
>
> Allen
>
> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
> September 19, 2013 1:25 PM
> 2013/9/19 Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>>
>
>         Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine
>         by me. It better expresses the intent, and the change should
>         only be observable to proxies.
>
>
>     Is this so? Wouldn't an ordinary object with a getter be able to
>     observe the two lookups? Indeed wouldn't the spec require this?
>
>
> You're right. The change would lead to observably different behavior 
> for conditionally invoked getters.
>
> Not sure how much of a backwards compat issue that would be. Allen 
> previously wrote upstream in this thread:
>
>     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'
>
>
> How likely is it that these are getters with side-effects?
> Tom Van Cutsem <mailto:tomvc.be at gmail.com>
> September 19, 2013 9:53 AM
> 2013/9/19 Allen Wirfs-Brock <allen at wirfs-brock.com 
> <mailto:allen at wirfs-brock.com>>
>
>     I really don't think we need to debate this much longer. We just
>     need to stay the course with [[Invoke]] and I can update the spec.
>     to replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for
>     this conditional situations. I may also added add a note
>     suggesting that the extra [[Get]] can be eliminated.
>
>
> Refactoring [[Get]]+[[Call]] to [[Get]]+[[Invoke]] seems fine by me. 
> It better expresses the intent, and the change should only be 
> observable to proxies.
>
> Re. the fact that |proxy.method.call(proxy)| would not re-bind |this|: 
> I've come to think that if |this|-rebinding is crucial to your proxy's 
> use case, you probably need to go "all the way" and just use 
> membranes. Membranes contain all the necessary logic to rebind |this| 
> as well as any other parameters.
>
> That said, I believe we could do strictly without invoke(), but given 
> that method invocation is so primary to JS, I believe we make the 
> right choice by exposing it in the MOP.
>
> By comparison, we also added a has() trap to trap the in-operator, 
> while we could have also just triggered a series of more fundamental 
> traps to figure out the result, and the in-operator is far less common 
> than method invocation in a typical JS program.
>
> Regards,
> Tom
> Allen Wirfs-Brock <mailto:allen at wirfs-brock.com>
> September 19, 2013 5:00 AM
>
> I've actually become convinced that [[Get]] and [[Invoke]] are the 
> correct primitives and and that the worry about "conditional invoke" 
> was a false concern.
>
> A method invocation such as:
> obj.foo(arg)
> is currently specified as being roughly equivalent to:
> obj.[[Invoke]]("foo", [arg], obj);
> and the ordinary implementation if [[Invoke]] decomposes into:
> let func = obj.[[Get]]("foo");
> let result = func.[[Call]](obj,[arg])
>
> However, a proxy may do other things in its 'invoke' handler including 
> replacing the this value and/or arguments passed to the [[Call]]. 
> There are strong use cases for the variability of such translations 
> which were the recent motivation for re-introducing [[Invoke]]. It 
> don't think we need to go around the loop of reconsidering those use 
> cases again. They are valid and we will end up at the same place.
>
> The problem is that we see within the ES specification a few instances 
> of a [[Get]]/[[Call]] sequence that look more typically like:
> let func = obj.[[Get]]("foo");
> let result;
> if (typeof(func) == "function") result = func.[[Call]](obj,[arg])
> else /* compute result some other way */
>
> Our concern started with" "oh no, the [[Get]]/[[Call]] is inside of 
> [[Invoke]] how can we get between them. This led to various proposals 
> for new MOP operations that split up [[Invoke]] or allowed a 
> conditional test to be injected into [[Invoke]]. I think this is the 
> wrong way to look at the problem. We were being mislead by legacy 
> [[Get]]/[[Call]] pattern and not looking at the actual conceptual 
> intent of these code sequences. In pure ECMAScrpt and dealing at the 
> level of object abstractions, this is what such use cases are really 
> trying to expression:
>
> If (typeof(obj.foo) == "function") result = obj.foo(arg);
> else //something else ...;
>
> or, in prose: If the 'foo' property of obj is a method, invoke that 
> method on obj with arg as the argument.
>
> or in pseudo-ES-pseudo code:
> let func = obj.[[Get]]("foo");
> let result;
> if (typeof(func) == "function") result = obj.[[Invoke]]("foo",[arg],obj)
> else /* compute result some other way */
>
> In other words, the appropriate conversion of the pattern we observed 
> in the ES spec. isn't [[Get]]+test+conditional call to [[Call]]. It is 
> [[Get]]+test+conditional call to [[Invoke]].
>
> From this thread, there are two concerns I anticipate. The first is 
> that a double property lookup of "foo" is being performed. 
> Conceptually I don't have a problem with that as the double property 
> access accurately represents the object level concept that is being 
> expressed. However, there might also be a perforce concern about the 
> double lookup, particularly for its usage in ToPrimitive. I believe 
> this is a non-problem as implementations can easily avoid performing 
> the double lookup it is an actual performance issue. A typical 
> specified usage of this pattern can be implemented something like:
>
> // If (typeof(obj.valueOf) == "function") return obj.valueOf();
> if (obj is not an ordinary object) goto slowpath;
> //in practice some sort of guard like this is likely to be used in 
> front of every MOP operation
> //A more specified test would be if object does not use both the 
> ordinary [[Get]] and [[Invoke]] implementations
> let func = InlinedOrdinaryGet(obj,"valueOf"); //and if "valueOf" 
> resolves to a getter, invoke it twice,yuck. Perhaps poison 
> optimization if any ordinary obj valueOf accessors defined
> if (func is an ordinary function object) return inlinedOrdinaryCall(obj);
> else if (func has a [[Call]] internal method) return 
> func.[[Call]](obj); //exotic function needs full MOP [[Call]] dispatch
> else goto nextcase;
> slowpath:
> //func is not an ordinary object so need to dispatch MOP calls on it
> //only slow path does double lookup
> let func = obj.[[Get]]("valueOf");
> if (func has a [[Call]] internal method) return 
> obj.[[Invoke]]("valueOf",[ ], obj); //full MOP [[Invoke]] dispatch
> nextcase:
> ...
>
> In other words, the cases in the specification for ordinary objects 
> can be implemented roughly like current implementations. No double 
> lookup need be performed and any extra guards are exactly the guards 
> that are needed to support the existence of proxies or other exotic 
> objects that over-ride [[Get]], [[Invoke]], or [[Call]].
>
> I'm not particularly concerned about double lookup performance for 
> similar use cases code in JS code. For ordinary objects, I expect 
> normal PIC mechanism to eliminate most of the double lookup overhead 
> and any hot code paths.
>
> However, a concern that was mentioned is that JS programmers routinely 
> express the conditional method call pattern as:
> If (typeof(func=obj.foo) == "function") result = func.call(obj, arg);
> rather than
> If (typeof(obj.foo) == "function") result = obj.foo(arg);
>
> I appreciate this concern. However, without having or using [[Invoke]] 
> this pattern may run into the same sort of bugs that motivated us to 
> recently add [[Invoke]]. We need to educate JS programmers that with 
> ES6 and the availability of Proxies and other features that obj.foo() 
> is not exactly the same thing as obj.foo.call(foo) and that the latter 
> formulation should generally be avoided except in expert situations. 
> the spread operator makes it particularly easy to use () instead of 
> apply The new "call" function is () and except for very special 
> circumstances where a different this values is being supplied 
> obj.method() is how methods should be invoked.
>
> I really don't think we need to debate this much longer. We just need 
> to stay the course with [[Invoke]] and I can update the spec. to 
> replace [[Get]]+[[Invoke]] rather than [[Get]]+[[Call]] for this 
> conditional situations. I may also added add a note suggesting that 
> the extra [[Get]] can be eliminated.
>
> Allen
>
>
>
>
>
>
>
>
>
>
>
> Jason Orendorff <mailto:jason.orendorff at gmail.com>
> September 17, 2013 2:36 PM
>
> Tom, it seems to me if there’s such a thing as “implementing the "get"
> trap correctly”, i.e. such that method calls work, that removes the
> main motivation for having [[Invoke]] in the first place. To recap
> what else [[Invoke]] achieves, in the light of that:
>
> * improved performance for proxies, because method calls go through
> one proxy handler trap rather than two, and no temporary function
> object is allocated
>
> * proxies can observe a bit about the caller (whether or not it's an
> [[Invoke]] call site), when a method is called
>
> If that's all, it seems like we should definitely remove [[Invoke]]
> and the .invoke trap. The MOP was already complicated enough. The
> performance argument is a non-starter, and the other “feature” is
> entirely undesirable.
>
> -j
>


More information about the es-discuss mailing list