__noSuchMethod__ and direct proxies

Dmitry Soshnikov dmitry.soshnikov at gmail.com
Mon Dec 5 03:29:23 PST 2011


On Mon, Dec 5, 2011 at 2:48 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

> Hi,
>
> There have previously been discussions on this list and elsewhere about
> whether or not proxies cover the __noSuchMethod__ use case [1,2]. In short,
> proxies can achieve anything the __noSuchMethod__ hook can, but requiring a
> proxy just to implement the hook is rightfully seen as heavyweight and
> could confuse code that has access to both the proxy and the wrapped object
> (since the proxy has a separate identity).
>
> In redesigning the Proxy API (direct proxies), we also redesigned the
> interaction between proxies and prototype inheritance, and it struck me
> that direct proxies, when used as prototypes, can actually trivially and
> elegantly support __noSuchMethod__ (I think MarkM deserves the credit, I
> remember it came up fairly early in the proxy design, but we never really
> pursued the idea).
>
> The trick is to implement a special proxy, let's call it a "MethodSink",
> and then have your object inherit from the MethodSink. Inheriting from the
> MethodSink is sufficient to enable the __noSuchMethod__ hook:
>
> var obj = { foo: 1 };
> obj.__proto__ = MethodSink; // note: the "beget" or <| operator would be
> of use here
> obj.__noSuchMethod__ = function(name, args) { return name; };
>

Just a note. Seems we're going to back to the unstratified API? When long
time ago in that long thread (
https://mail.mozilla.org/pipermail/es-discuss/2010-October/011912.html)
with `isCall' flag for `get', I was showing a similar example with
`__noSuchProperty__' and `__noSuchMethod__' (
https://github.com/DmitrySoshnikov/es-laboratory/blob/master/examples/noSuchMethod.js),
you were against such __magicAssignments__ to objects. Though, in my case
those were proxies, created by alternative protocol: `Object.new'.



> obj.foo // 1
> obj.bar() // "bar" (triggered the noSuchMethod hook)
> obj.toString // Object.prototype.toString (methods inherited from
> Object.prototype remain available)
>
> (Note: obj.bar no longer evaluates to "undefined" but rather to a
> "function(...args){...}" that, when called, triggers the __noSuchMethod__
> hook.)
>

Yep, but there is no need to create a new function in each `get' trap. A
special "activator" funciton is enough (see the link above with
implementation).


>
> So how does this work? I tested the following in Firefox 8, using my
> DirectProxies shim [3] that implements the new Proxy API in terms of the
> old Proxy API:
>
> Object.prototype.__noSuchMethod__ = function(name, args) {
>   throw new TypeError(name + " is not a function");
> };
> MethodSink = Proxy({}, {
>   has: function(target, name) { return true; },
>   get: function(target, name, receiver) {
>     if (name in Object.prototype) {
>       return Object.prototype[name];
>     }
>     return function() {
>       var args = Array.prototype.slice.call(arguments);
>       return receiver.__noSuchMethod__(name, args);
>     }
>   }
> });
>

Again, see note on "activator".



>
> It's easy to parameterize the above MethodSink abstraction to work with an
> arbitrary prototype object rather than Object.prototype, if your
> __noSuchMethod__-enabled object needs to inherit from something other than
> Object.prototype.
>
> One could take this a step further and mutate Object.prototype.__proto__
> to install a MethodSink as a "new prototype root".
>

And then we completely back to unstratified Python's style __magicNames__ (
https://github.com/DmitrySoshnikov/es-laboratory/blob/master/examples/hook.js).
Though, I'm not so against ;) Espessialy if these magic names will be
filtered in methods such as `Object.keys', etc.



> This is, however, broken, as it would change the result of |obj.name| for
> any object obj and any missing name from "undefined" to "function(){...}",
>


Ahh, so many times I repeated the same in that long thread with `isCall' on
what you told that this is OK, since "object knows how to handle its
properties". But in general it can be considered as broken invariant, as I
noted that time, yes.



> which is guaranteed to break existing code. As was mentioned before on
> this list, proxies cannot distinguish property get |o.foo| from property
> invocation |o.foo()|, so must assume that all property gets may be
> immediately followed by function application.
>

To support `apply' and `call', yes, unfortunately it's hard to handle
`isCall'. Though, in general it's possible -- just a programmer should know
then that she can't use a method in call/apply if handles the result
directly in the proxy's `get', but not returns "activator" function to the
outside.



>
> What could work, however, is to replace the __noSuchMethod__(name,args)
> hook by __noSuchProperty__(name), which would be a hook that by default
> returns "undefined", but can be overridden to return any value, including a
> function to emulate missing "methods". Given Javascript's "invoke = get +
> apply" nature, this would actually be the proper "doesNotUnderstand" hook
> for Javascript.
>

Yes, I used the same approach in the link above. First noSuchProperty is
activated, then the "activator" is returned and, then, if the activator is
called, then noSuchMethod. Of course if noSuchMethod is set. Otherwise it
can return `undefined'.



>
> That said, I would argue against installing MethodSink as the global
> "root" prototype.
>

If (hypothetically) to install it, then not to install it, but directly
make all objects as proxies :) That is, w/ non-stratified meta-hooks. But,
we all know that we wanted to leave this practice (though, it's not so bad
in some cases).



> My hunch is that it will have a large impact on the entire
> page/environment, affecting all property access (and in particular, all
> property assignment, which must trigger that proxy's "set" trap, regardless
> of whether the receiver object already contains the property, in search for
> inherited setters).
> __noSuchMethod__ or the hypothetical __noSuchProperty__, if installed
> globally, are not "pay as you go" features: they affect every object
> regardless of whether it implements the hook. The solution shown above
> where an object deliberately activates the hook by delegating to a
> MethodSink prototype is much cleaner: only the objects inheriting from the
> MethodSink pay for the overhead. Regular objects with no proxies in their
> prototype chain don't.
>
>
This is why I created alternative `Object.new' (objects with meta-hooks) in
contrast with `new Object' (simple objects w/o proxies overhead).

But in general, objects are themselves proxies at implementation level (I
mean internal [[Get]], etc) ;) So perhaps, it's not so big overhead.



> The MethodSink abstraction provides the best of both worlds: a) like
> __noSuchMethod__, there is no need to turn your object into a proxy, and b)
> unlike __noSuchMethod__, but like proxies in general, the feature is
> pay-as-you-go: no overhead if your object doesn't delegate to the
> MethodSink.
>
>
And 3) don't forget that you have returned unstratified meta-API against
which you so was. Other things are OK for me.

Dmitry.


Cheers,
Tom

[1] https://mail.mozilla.org/pipermail/es-discuss/2011-October/017467.html
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=683218#c8
[3]
http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js

______________________________
>
> _________________
> es-discuss mailing list
> 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/20111205/843dbdc6/attachment-0001.html>


More information about the es-discuss mailing list