__noSuchMethod__ and direct proxies

# Tom Van Cutsem (12 years ago)

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] esdiscuss/2011-October/017467 [2] bugzilla.mozilla.org/show_bug.cgi?id=683218#c8 [3] code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js

# Dmitry Soshnikov (12 years ago)

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 ( esdiscuss/2010-October/011912) with isCall' flag forget', I was showing a similar example with __noSuchProperty__' andnoSuchMethod' ( 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 ( 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' andcall', 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'sget', 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 withnew 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] esdiscuss/2011-October/017467 [2] bugzilla.mozilla.org/show_bug.cgi?id=683218#c8 [3] code.google.com/p/es-lab/source/browse/trunk/src/proxies/DirectProxies.js

# Tom Van Cutsem (12 years ago)

2011/12/5 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>

Just a note. Seems we're going to back to the unstratified API? When long time ago in that long thread ( esdiscuss/2010-October/011912) with isCall' flag forget', I was showing a similar example with __noSuchProperty__' andnoSuchMethod' ( 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'.

Just to make sure I am not misunderstood: no, "we" are not going back to the unstratified API!

noSuchMethod remains as unstratified as it ever was. I maintain that the stratified Proxy API is safer than noSuchMethod. The above was simply an answer to the question as to whether proxies might provide a way to support noSuchMethod without turning your object into a proxy. They do, but that doesn't make noSuchMethod any more principled.

So, FTR, I still prefer using a proxy with a |get| trap over a noSuchMethod hook ;-)

# Dmitry Soshnikov (12 years ago)

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

2011/12/5 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>

Just a note. Seems we're going to back to the unstratified API? When long time ago in that long thread ( esdiscuss/2010-October/011912) with isCall' flag forget', I was showing a similar example with __noSuchProperty__' andnoSuchMethod' ( 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'.

Just to make sure I am not misunderstood: no, "we" are not going back to the unstratified API!

noSuchMethod remains as unstratified as it ever was.

You mean you don't propose to standardize it or sort of? I.e. it's just a "library" variant of how one can implement `noSuchMethod'? Oh, then it's OK, since there can be many own implementations in particular libraries.

I maintain that the stratified Proxy API is safer than noSuchMethod.

Yup, especially if it can be achieved also with convenient "assignments", but still noSuchMethod will be meta-property and won't exist on the object ( DmitrySoshnikov/es-laboratory/blob/master/examples/meta.js )

The above was simply an answer to the question as to whether proxies might provide a way to support noSuchMethod without turning your object into a proxy.

I see, OK ;) It's a good variant of implementation.

They do, but that doesn't make noSuchMethod any more principled.

So, FTR, I still prefer using a proxy with a |get| trap over a noSuchMethod hook ;-)

k, got it

Dmitry.

# Tom Van Cutsem (12 years ago)

2011/12/5 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>

You mean you don't propose to standardize it or sort of? I.e. it's just a "library" variant of how one can implement `noSuchMethod'? Oh, then it's OK, since there can be many own implementations in particular libraries.

Exactly.

# John J Barton (12 years ago)

On Mon, Dec 5, 2011 at 2:48 AM, 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]. ... [1] esdiscuss/2011-October/017467 [2] bugzilla.mozilla.org/show_bug.cgi?id=683218#c8

The references here discuss how proxies can/cannot implement noSuchMethod. Is there a use case for noSuchMethod? As in why would anyone want such a thing?

Thanks, jjb

# William Edney (12 years ago)

John -

Well, obviously I have use cases or otherwise I wouldn't have put the time and effort into continuing to ask for this functionality.

As I've said before, as long as I can hook proxies into the prototype chain such that I can use proxy functionality with objects where I don't have control over their creation, I would happily get rid of a non-standard solution like noSuchMethod. The threads you mention have snippets of code that I use in real world applications all-day, every-day. Unfortunately, I can only use an elegant solution with Firefox right now, since its the only one that has noSuchMethod. For other platforms, I must use much hackier solutions.

# Dmitry Soshnikov (12 years ago)

On 05.12.2011 20:20, John J Barton wrote:

On Mon, Dec 5, 2011 at 2:48 AM, 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]. ... [1] esdiscuss/2011-October/017467 [2] bugzilla.mozilla.org/show_bug.cgi?id=683218#c8 The references here discuss how proxies can/cannot implement noSuchMethod. Is there a use case for noSuchMethod? As in why would anyone want such a thing?

Dynamic abstractions. A real example Ruby on Rails' database lookup methods.

Say, you have a model with fields, [id, name, email], then in your db model you have methods:

db.find_by_name, db.find_by_email, etc. Once you add another field to your db, you automatically have its getter either.

And all these methods are virtual. Though, rails uses "once-called-virtual-method" approach. Which means the method (for efficiency) will be created on object/prototype.

Dmitry.

# Oliver Hunt (12 years ago)

IIRC the Cappuccino has its own method dispatch implementation solely to support Obj-Js noSuchSelector (or whatever it's called) method. They take quite a perf hit because of that, and it would be unnecessary if there were someway to implement a noSuchMethod analog. I suspect the direct proxy in the protochain would do the trick,