__noSuchMethod__ and direct proxies

Tom Van Cutsem tomvc.be at gmail.com
Mon Dec 5 02:48:29 PST 2011


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

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);
    }
  }
});

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". 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(){...}", 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.

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.

That said, I would argue against installing MethodSink as the global "root"
prototype. 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.

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.

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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20111205/25176bd2/attachment.html>


More information about the es-discuss mailing list