Proxies: get+fn vs. invoke

Dmitry A. Soshnikov dmitry.soshnikov at gmail.com
Mon Oct 11 07:57:08 PDT 2010


  On 11.10.2010 18:18, Tom Van Cutsem wrote:
> This is odd, I didn't see the original message you were replying to.

Yes, really odd. It's not the first time when the archive-server looses 
letters.

OK, here is original letter sent before (the main question is not in 
"magic methods" directly on object, it's just a academic curiosity, but 
the main question is -- how in one /get/ method of the handler to manage 
missing properties and missing methods?) :

-------- Original message -----

Hi,

I continue experiments with proxies (the first one was delegation-based 
mixins). Currently (only for academic curiosity) I've implemented 
objects with "magic hooks" which are defined "directly" on the objects. 
Actually, it's just a "proxied proxies", and there is no much sense in 
it (except maybe that such objects syntactically are shorter than real 
proxies), but repeat, the reason is -- only an academic interest of how 
do Proxy objects work. The script is here: http://gist.github.com/617338

But this letter is not about the script (the script is just a reason), 
but about Proxy objects themselves. I'd like to clarify some features.

First, unfortunately, because of stratification of 
/Proxy.create(handler, proto)/ and /Proxy.createFunction(handler, 
callTrap, constructTrap)/ I can't manage in my "sugar" /typeof/ and 
/[[Class]]/ of a proxy object (depending on presence of /__call__/ or 
/__construct__/ -- in one case /typeof/ should result "function", in 
other case, if I later remove both /__call__/ and /__construct__/, it'd 
nice to have "object"). Yes, it's quit clear that it's related with 
/Function.prototype/ which is (should be?) set as a prototype of the 
function proxy. This question is not so essential, because I understand 
that the proxy strawman's idea assumes that a user will work with 
proxies, but not with directly defined (not stratified from an object) 
meta-methods, so I just mention this fact (that it turned out 
inconvenient for my script) :) Although, possibly someone will want to 
use this Python's "fancy" /__name__/s "sugar".

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).

Of course, /get/ method of the handler may return (possibly creating 
every time new) a function and then we have 
/__noSuchMethod__/implementation. That's OK (having this approach we 
have "funargs + call/apply" scheme). But unfortunately, we can't 
distinguish what contains a call-site:

Is it a property reading:

foo.bar

or possible invocation:

foo.bar()

How should /get/ trap of the proxy be implemented to support both: 
/__noSuchProperty__/ and /__noSuchMethod__/ ? This is the main question. 
Seems, there is no obvious way.

On the other hand, regating "funargs + call/apply" scheme: if the /get/ 
trap returns each time a new function, then === invariant is broken. 
That means it should return some predefined function from available for 
handling function-table (i.e. if name is "foo" -- return table.foo, 
etc). It of course can dynamically define the function and save it in 
the table. And actually to keep === invariant, it should do so.

Dmitry.

-------- End of original message -----

>
> To respond to the question you posed in the "syntax for efficient 
> traits"-thread:
>
>     Yes, I do. The latest is "magic methods" directly on objects
>     (possibly you saw -- http://gist.github.com/617338; I don't know
>     what again with es-archive server, but it doesn't display the
>     lettter which I send about "get+fn vs. invoke"
>     --https://mail.mozilla.org/pipermail/es-discuss/2010-October/thread.html --
>     there only answer on the letter with correcting the typo).
>
>
> Hmm, your API defines all the handler methods directly on the object, 
> thus explicitly ignoring the stratification afforded by the original 
> API. Granted, your implementation filters out magic properties in 
> appropriate places, so they won't show up in e.g. for-in loops, but 
> the magic methods are still easily accessible directly from the 
> object. I understand that people just want to write:
>
> myObject.__get__ = function() { ... }
>
> instead of:
>
> var handler = Proxy.noopHandler(myOriginalObject);
> handler.get = function() { ... }
> myObject = Proxy.create(handler, Object.getPrototypeOf(myOriginalObject));
>
> I agree that the current API is too bulky if all you want is 
> __noSuchMethod__, but I'd much rather see a wrapper library that 
> allows me to write something like this:
>
> var [myObject, handler] = wrap(myOriginalObject);
> handler.get = function() { ... }
>
> or just:
>
> var myObject = wrap(myOriginalObject, { get: function() { ... } })
>
> This still keeps all the meta-functionality of an object in a separate 
> object. Code that only has access to "myObject" can't mess around with 
> its meta-level behavior. 'magic' methods don't prevent a client from 
> inadvertently overriding an object's traps.
>
>

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. 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?

Returning a function approach has advantages:

1. returned method can be extracted as a functional object (and e.g. 
passed as a funarg);
2. we have working call/apply invariant, i.e. foo.bar.apply(...) is 
foo.bar(...)

But what if the user wants to trap get for non-existing /properties/ and 
non-existing /functions/? Here e.g. /get/ function from my implementation:

/**
* generic [[Get]]
*/
handler.get = function (r, name) {

   // __get__ hook
   if ("__get__" in object) {
     object.__get__(name);
   }

   // if a property is not found
   if ("__noSuchProperty__" in object && !(name in object)) {
     return object.__noSuchProperty__(name);
   }

   // here I don't know how to handle a method call-site

   return object[name];
};

foo.bar;  // __get__ bar
foo.nonExisting;  // __get__ nonExisting, __noSuchProperty__ nonExisting
foo.baz(); // __get__ baz
foo.nonExisting(); // *HOW CAN I MANAGE IT?*

I don't know what a call-site contains -- a function invocation or a 
property reading, so I can't in one get return a function or a property.

There can be /invoke/ trap which traps /all/ invocations:

foo.bar(); // __invoke__ bar
foo.nonExisting(); // __invoke__ bar

But then it will be hard to distinguish it from /get/ which returns a 
function.

On the other hand, there can be not /invoke/, but /noSuchMethod/ 
(/methodMissing/, /doesNotUnderstan/ -- call it as you wish) -- /exactly 
method/, i.e. a /TypeError "not a function"/. Then we can combine both: 
get+fn and noSuchMethod:

get: function (r, name) {
   if (name == "bar") {
     return function () {};
   }
   return object[name];
}

then we have:

proxy.bar; // function
proxy.baz; // object.baz
proxy.bar(); // OK -- get + fn -> call
proxy.nonExisting() // fail, get+undefined -> call: TypeError "not a 
function"

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.


Dmitry.


>     Maybe you can answer on this question regarding proxies? How can
>     we handler in one get of a proxy handler both cases of a call-site:
>
>
>     foo.bar
>
>
>     and
>
>
>     foo.bar()
>
>
>     ? 
>
>
>     Having invoke, we handler foo.bar(). However, the case when
>     the get returns should look (unfortunatelly?) like (foo.bar)().
>     Also of course foo.bar does not extracted as a functional object
>     (including invariants foo.bar.call/apply). But I think, is it the
>     case that user wants to used them exactly as funargs or he wants
>     more to handle just access (exactly calling of) to non-existing
>     methods? P.S.: to avoid off-topic regarding this topic devoted to
>     traits/mixins, it's better to answer to that trhead -- "get+fn vs.
>     invoke".
>
>
> In short: the 'get' trap can't distinguish the above two cases. This 
> is a pity, and I agree it would be useful for 'get' to have that 
> information sometimes. There has previously been talk on this list of 
> parameterizing 'get' with an additional flag to detect get+call vs 
> invoke, but it turned out that not all implementations would be able 
> to support it: 
> <https://mail.mozilla.org/pipermail/es-discuss/2010-May/011062.html>
>
> In retrospect it may not be so bad that this feature isn't supported. 
> I can imagine it could lead people to write code like:
>
> get: function(receiver, name, args) {
>   // suppose that args === undefined implies that the property was 
> only queried, not invoked
>   if (args === undefined || args.length === 0) {
>     return 'a';
>   } else {
>     ...
>   }
> }
>
> So that obj.name <http://obj.name> and obj.name <http://obj.name>() 
> would both return 'a'. This could lead to a lot of confusion, since 
> it's hard to quantify what "obj.name <http://obj.name>" denotes. If 
> it's supposed to be a method, then obj.name <http://obj.name> should 
> return a function. If it's supposed to be a getter, then obj.name 
> <http://obj.name>() should call the thing returned by the getter 
> (which, in this case, should raise a type error since strings are not 
> callable)
>
> Cheers,
> Tom
>
> 2010/10/9 Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com 
> <mailto:dmitry.soshnikov at gmail.com>>
>
>     On 10.10.2010 0:44, Dmitry A. Soshnikov wrote:
>>
>>
>>     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:
>
>     Sorry, typo, not cons, but pros of course.
>
>     Dmitry.
>
>
>     _______________________________________________
>     es-discuss mailing list
>     es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>     https://mail.mozilla.org/listinfo/es-discuss
>
>

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


More information about the es-discuss mailing list