Proxies: get+fn vs. invoke

Dmitry A. Soshnikov dmitry.soshnikov at gmail.com
Mon Oct 11 11:30:25 PDT 2010


  On 11.10.2010 20:07, Tom Van Cutsem wrote:
>
>     The most interesting question for me is mentioned several times
>     (the last one was by Brendan in his slides from the lates JS Conf)
>     "get + defining Fn" vs. invoke trap.
>
>
>     Yes, there are some cons: a non-existing method can be extracted
>     as a funarg for later use. It also do not break invariant with
>     call/apply methods applied for the method:
>
>     var foo = proxy.nonExistingMethod;
>
>     Thus, /foo/ is a real function and also
>     /proxy.nonExistingMethod()/ is the same as
>     /proxy.nonExistingMethod.call(undefined)/.
>
>     However, I don't see how can I implement a generic /get/ method in
>     the proxy handler? I.e. I'd like to trap at the same time both:
>     reading simple properties and also invocation of methods. In my
>     script these are: /__get__/ (traps reading of all properties),
>     /__noSuchProperty__/ (traps reading only of missing properties)
>     and /__noSuchMethod__/ (traps methods invocations).
>
>
> Ok, so all of this is inspired by Smalltalk's "doesNotUnderstand:" 
> trap, and derivatives, right?
>
> The thing is: in Smalltalk, there is no property access, only message 
> sending (method invocation). Thus, there can be only missing methods, 
> not missing properties.

Yeah, the same as in ruby with its /method_missing/ -- there also public 
API of an object is a set of accessor methods (and there are only 
methods) and instance variables are not available from the outside 
without having an accessor.

> In Javascript, there is only property access, no true method 
> invocation. Thus, there can be only missing properties, not missing 
> methods.
>

Yeah, I know.

> So I think the simple answer is: you can't have both. In Javascript, 
> __noSuchProperty__ trumps __noSuchMethod__

Yes, I know and understand it. Though, was asking about possible 
solution (with analyzing a call-site context).

>
> It would be analogous to asking for a "doesNotUnderstandProperty" trap 
> in Smalltalk: such a trap would never get triggered, since "obj foo" 
> is a message send in ST, not a property access.
>
>     Yes, everything correct with stratification of the meta- and
>     normal- levels; I know that this is the main reason. As I said
>     (and repeat it again) -- my implementation with
>     __Ugly?PythonsNames__ is just an academic curiosity -- to play
>     with proxies.
>
>
> Sorry, I didn't know what your intentions were. Academic curiosity is 
> good ;-)
>
>     And was playing with them, I backed to the ideological dilemma
>     which was mentioned also several times: "get + fn vs.
>     noSuchMethod/invoke". This is what Brendan mentioned before and
>     also in the last slides of the recent JS Conf (I saw only slides,
>     I didn't see yet video of Brendan's talk -- maybe he already
>     answered this question? However, I didn't find the answer in slides).
>
>
>     The main (42? ;)) question is: how having /one get/ method in a
>     proxy's handler to handle both cases of a call-site -- a /property
>     reading/ and a /method invocation/ ?
>
>
>     foo.bar
>
>     and
>
>     foo.bar()
>
>     Currently I understand, that implementation of the
>     __noSuchMethod__ described in the strawman article is just wrong
>     -- because /get/ method of the handler /always/ returns a
>     /function/. That means, /foo.bar/ - is a function, /foo.baz/,
>     /foo.whatTheHack/ - is also a function. How a user will
>     differentiate accesses to a non-function properties to a function
>     properties?
>
>
> As I understand it, even spidermonkey's __noSuchMethod__ does not 
> currently allow one to tell the difference,

No, it does allow. It exactly analyzing the context of a call-site 
(whether it is a simple property reading or besides the reading the 
property there is also a call expression). I can simply add 
/__noSuchMethod__/ to my implementation and the method will 
automatically be called (btw, have just added, you may test it). First 
of course will be called the /get/ method of the handler with property 
name /__noSuchMethod__/ and if an object has such a function, it will be 
called with needed method name and needed arguments.

As I said, I can even implement it (again -- without a big practical 
need, but just 'cause of academic curiosity) without native 
SpiderMonkey's /__noSuchMethod__/ (e.g. naming it /methodMissing/) -- 
all is needed is to catch /TypeError/ with a message /"not a function"/ 
which will provide needed reference's components (a base and a property 
name to call); unfortunately, it's harder with arguments.

So /__noSuchMethod__/ does catch this case. And exactly in the right way 
-- it handles /non-existing property/ and only if there is a call 
expression in the call-site. On the other hand (and again correctly) -- 
it does /not/ handle /existing properties which are not functions /(you 
again may check in the updated script, I've added tests).
/
//
/
> / /so I don't think the strawman implementation is "wrong" in this regard.

It's wrong because it's always returns a function. Which means with such 
a proxy (which is a prototype of a proxied object) -- all you can is 
just call these functions. Regarding current implementation on the 
strawman proxies page: first, the invariant === is broken (since always 
a new function is returned). But this is not so essential, as I 
mentioned, theoretically it may satisfy === invariant, returning each 
time the same function (with saving a newly created function in some 
dispatching table), but practically it's unsoundly complicated and 
inconvenient. Second, that is more essential, repeat -- with such an 
implementation, all we can is just to call these our function, which 
brings us to broken case if e.g. /foo.toString()/ is called, which 
should be found in e.g. /Object.prototype/.

Of course this case can be managed (for example, to check first, whether 
the /name in object/: if not -- return our wrapper function, if it is -- 
return object.toString). However, with such object we can't manage 
/being outside/ (i.e. neither in /createHandled/ scope, nor in the proxy 
handler) the case of dynamic conditional property definition: i.e.

if (!("foo" in object)) {
   // ad-hoc implementation of object.foo
}

But it will never be undefined, since it's always defined and is a new 
function which call hoSuchMethod trap.

> With the current __noSuchMethod__, non-existent properties will show 
> up as "undefined" values when queried via property access.

As I mentioned, current __noSuchMethod__ of SpiderMonkey works as needed 
and expected.

> In contrast, the strawman implementation would return a function.

Yes, and there are exact issues with it which I also mentioned above.

> Neither is satisfactory if the user expects a non-function value.
>
Well, yeah. But the case with if (!foo.bar) foo.bar = {...} is here for 
years, and with this implementation it's broken.

> What you are asking for is a hook that returns a different value 
> depending on its context of use (property access vs method call syntax).

Yes, indeed and exactly.

> As it stands, the Proxy API does not support this use case.
>
Well, so much the worse for Proxy API.

>     And exactly the last case can be trapped with noSuchMethod. I can
>     even implement it in my script (e.g. catching onError with
>     debugger service, or even simple window.onerror -- though will be
>     hard with arguments). But it can be additional trap for a proxy:
>
>     noSuchMethod: function (name, args) {
>       console.log(name, args);
>     }
>
>     then:
>
>     proxy.nonExisting(1, 2, 3); // get "nonExisting" -> call-site
>     contains call expression -> noSuchMethod("nonExisting", [1, 2, 3])
>
>     The issues are also known:
>
>     proxy.nonExisting(1, 2, 3); is not the same as
>     proxy.nonExisting.apply(null, [1, 2, 3]) and we can't extract
>     proxy.nonExisting as a function ('cause obviously it's not a
>     function). But what is more convenient for a user -- to catch such
>     methods or to keep invariants with apply/funargs? Also, as I
>     mentioned, even if it is done via get, then /to keep ===
>     invariant/, get should return always the same missing function for
>     a property (which means to keep some dispatch table). So, anyway,
>     some invariants will be lost anyway (or will be
>     complex/inconvenient in implementation) and it's needed to choose
>     what is better for a user. I think it will be good to have
>     additionally for proxies noSuchMethod trap.
>
>
> I think this proposal can be reduced to the previous proposal of 
> parameterizing the 'get' trap with a flag that indicates context of use.

Oh, I wasn't aware about the proposal with the ad-hoc/parametrized get. 
Why didn't you say from the beginning? It's completely enough if we can 
distinguish a call-site context (i.e. simple property read or with a 
followed call expression). But from this viewpoint, it just the alias 
for the noSuchMethod. It's better to have it separated from the 
parametrized get.

get: function (rcvr, name, isCall) {
   // handle reading with call expression
   if (isCall) {
     // btw, where are arguments of a call? another parameter?
   }
   // handle simple reading
}

or

get: function (rcvr, name) {...}
noSuchMethod: function (name, args) {...}

> Since we did not make progress on that feature, I'm afraid a dedicated 
> 'noSuchMethod' hook will not fundamentally change things.
>

Where this proposal is described? I didn't see it. I'll support it. Even 
if the committee won't agree on noSuchMethod, it'd at least will be 
great to have a parametrized get. Though, repeat, IMO, a separated 
method for this case is better.

Dmitry.

> Cheers,
> Tom

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101011/8a01e291/attachment-0001.html>


More information about the es-discuss mailing list