My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Tuesday, December 13, 2011

Please, Give Us Back __noSuchMethod__ !

For those who don't know what __noSuchMethod__ is here the quick summary: it was a bloody handy non-standard method able to provide a fallback whenever we invoked an object method that did not exist.

var o = {};
o.__noSuchMethod__(function (name, args) {
alert(name); // "iDoNotExist"
alert([].slice.call(args)); // 1,2,3
});
o.iDoNotExist(1, 2, 3); // will produce above alerts


A Bit Of Background

Well, if you are patient enough, you may consider to read this never-ending post in Mozilla mailing list.
The reason that post is called Proxies: get+fn vs. invoke is because Proxy supposes to be the new way to go able to bring us much more power than we probably ever need ... but hey, this is welcome, while what is not welcome, is that Proxy may not be implemented first, which means browsers vendors should have waited to remove __noSuchMethod__ 'cause right now we may not have a pseudo equivalent, and second, but surely not less important, , Proxy does not provide the same functionality.

The Minified Theory Against The Practice

The main argument from @BrendanEich is that JavaScript has properties only so that o.fn() is the equivalent of o.fn.apply(o).
While this is true with any normal object, this is totally different with __noSuchMethod__.
The equivalent of __noSuchMethod__ for that operation, and behind the scene, is:
  • is there a property in o or its __proto__ called fn ?
    • yes, proceed as usual as if it was o.fn.call(o) and throw error if that property was not callable
    • no, is there a __noSuchMethod__ callback to burn instead of throwing an error due undefined property?
      • yes, perform the current operation: nsm_callback.apply(o, arguments)
      • no, throw an error since property was undefined and obviously not callable
Got it? The equivalent of o.fn() in an environment where __noSuchMethod__ was supported is potentially different from o.fn.apply(o) ... I am 100% sure Brendan knows this before and better than me and this is the reason I don't really get his strongest point.
Once again, o.fn() may be the equivalent of nsm_callback.apply(o, arguments) and not o.fn.apply(o).

The Inexistent Theory Against The Practice

If above "reason" was not enough, I have read even worst in the same thread. I am sorry guys, but sometimes you must be realistic and understand that if a developer does, as example, this:

// de-context fn from o and invoke it
(o.fn)();

// exact equivalent of
var fn = o.fn; // GETTER, no invokation
// and after ...
fn()
// this is a problem? we have same with missing bind then ...

rather than this:

// invoke fn through o as default context
o.fn();
// can we see the difference?

it means that developer has much bigger problems than __noSuchMethod__ inexistent ambiguity, that developer does not even know that __noSuchMethod__ exist ... come on!
Going on and on, another point is that get should be all we need to simulate the __noSuchMethod__ behavior through proxies ... but this is completely misleading!

A Getter IS A Getter

Is that trivial ... if we access a generic object property we are doing nothing different from invoking a getter with such object as property context.

o.whatever;
// look for "whatever" property name in o
// if found returns the "whatever" associated value


o.whatever.call
// nothing change, THIS IS NOT AN INVOKE
// look for "whatever" property name in o
// if found returns the "whatever" associated value
// since the value was a function, the call method is usable
// if whatever was not defined, the call method won't exist

Nobody should ever even consider to use property accessor and expect a __noSuchMethod__ behavior ... that property did not exist, what kind of method would you expect to look for?
call is a property of the Function.prototype so following the accessor/getter logic, nothing is ambiguous here.
Accordingly, lattest example is simply an inexistent mistake that hopefully no developer would ever do ... but you know, shit happens, then we learn, then hopefully we don't repeat same shit.

Other Programming Languages

When it comes to PHP, they perfectly managed to make the behavior not ambiguous through the __call magic keyword in classes definition but no, we decided that in JavaScript we cannot even think to put an invoke to make the life easier and completely NOT ambiguous for all of us ... do we?
I still cannot understand where and what is the ambiguous part if we have an explicit invoke declaration ... maybe something a bit harder to solve behind the scene for these poor JS engines? It could be ... should we all limit JS because of this? I don't think so.

Think About Libraries APIs Migrations

I give you the most basic example, the most used JS library with a fake getter and setter behavior: jQuery.

// jQuery simulation of getters and setters behaviors

// the getter
$("body").html(); // return string with content

// the setter
$("body").html("
whatever
");
// set the string with content

If you want, specially for chainability reasons through the simulated setter, the fact html is a method is convenient for the library but this library is stuck forever behind these two methods.
jQuery, at current ECMAScript status, will never be able to switch gradually to real getters and setters ... why that? A simple example:

$("body").html; // returns the string in jQuery 3000

$("body").html(); // shows a "deprecated warning"
// ... and returns the "html" getter

// implementation example
function setInnerHTML(node) {
node.innerHTML = this;
}
Object.defineProperty($.fn, "html", {
get: function () {
return this[0].innerHTML;
},
set: function (html) {
this.each(setInnerHTML, html);
}
});

// the deprecation warning
$.fn.__noSuchMethod__(function (property, args) {
if (property in this) {
console.log("Warning: " + property + " is not a method anymore");
if (args.length) {
// invoke the setter
this[property] = args[0];
// preserve behavior
return this;
} else {
// invoke the getter
return this[property];
}
} else {
throw "Y U NO READ DOCUMENTATION";
}
});

That's it, we can migrate from two different APIs implementing getters and setters whenever we had a similar behavior and bringing gracefully users to the new usage ... no wa can't!

It Is Not About jQuery

I don't even use jQuery so don't get me wrong, this is not my battle here ... the point is that for another private project I am working on I would like to educate developers to use properties correctly but I understand developers may already got use to invoke methods as if it is normal, even when they are simply looking for a getter behavior.

var o = {
whatever:"cool bro",
__noSuchMethod__: function (property, args) {
console.log("we got a bro-blem here, " +
"don't invoke if you want a getter");
return this[property];
}
};

// so that
o.whatever === o.whatever();

Secially last line of code is apparently impossible to reproduce with Proxies, those that suppose to be the new and best way to go, those that give us control on things rarely needed until now, those that made JS.Next group decide that __noSuchMethod__ was evil and it had to be abandoned.
I really hope that JS.Next will not be non-developer expectations behaviors driven because guys, somebody tries to do cool things with this cool language, and if the reason you drop something is because we are all morons, as example misunderstanding the difference of a referenced property through parenthesis ... oh well ... good luck kkfuture JavaScript ...

How To Solve This

Please put an invoke or even better an invokeProperty, preserving invoke for when the object itself is used as if it was callable, in the current Proxy specifications so that who knows what is doing, can keep doing it and who never even bothered with this stuff, won't be affected at all.
Thank you for listening.

16 comments:

David Bruant said...

"Please, Give Us Back __noSuchMethod__ !"
"browsers vendors should have waited to remove __noSuchMethod__ "
=> Which browser removed it? Isn't it a Firefox specific feature? http://kangax.github.com/es5-compat-table/non-standard/


"(o.fn)()" vs "o.fn()"
"(...) it means that developer has much bigger problems than __noSuchMethod__ inexistent ambiguity, that developer does not even know that __noSuchMethod__ exist ... come on!"
=> I know __noSuchMethod__ and use the former pattern in a lot of my code.
Obviously not in the same form. But I happen to write very often things like:
----
var isArray = Array.isArray;
isArray([1,2,3]);
----
You are invited to tell me that I have big problems with using the functional nature of JavaScript.


"inexistent ambiguity"
=> The ambiguity exists, otherwise, the "never ending" thread wouldn't have been concluded the way it has.


Comparison with PHP
=> As far as I know, functions are not first-class objects unlike in JavaScript. This makes a tremendous difference.

Andrea Giammarchi said...

in PHP you can return a closure through a getter ... how do you like that?

Andrea Giammarchi said...

also assigning and expecting a __noSuchMethod__ in that way ... looks like you have some problem with bind() as well then, you know what I mean?

bind() is ambiguous the same way then 'cause if you forget to bind the function what context you want?

... these points are all "meh" ... imho ...

Andrea Giammarchi said...

last, __degineGetter__ and __defineSetter__ have been adopted and as many other things where FF only ... __noSuchMethod__ has been removed and is still not replaceable in current FF, isn't it?

Don't burry features please, and consider not all developers do those mistakes with assignments, thank you.

hristo said...

Hey,

I think there's a one big issue here, which is that typeof = function dont work, you cant bind() fake methods, and i bet runtime checks undermine performance

Imagine i bind the html method to some selector and use that as a callback somewhere - the deprecation move will break that

You can do deprecation warnings with much more ellegance by using simple functions, even dynamically generated from a space separated list of property names, and MUCH more safely. Functions are a good thing, and __nosuch breaks the functional paradigm, and as such strikes me as something that hurts js

Andrea Giammarchi said...

no issue at all ... typeof o.fn checks the getter

typeof o.fn() check the returned value from the __noSuchMethod__

we should know these things

__noSuchMethod__ is NOT a function, is a fallback on a function invoke through an object

Tom Van Cutsem said...

Have you read https://mail.mozilla.org/pipermail/es-discuss/2011-December/018834.html ?

In that post, I explain how proxies can be used to implement __noSuchMethod__, with the important caveat that all missing properties will show up as functions, rather than "undefined", when accessed without being invoked. Other than the API migration issue you point out, I don't see that as a drawback: it means that the proxy-based __noSuchMethod__ implementation will work properly with functional code:

// assume o defines __noSuchMethod__ using proxies
var f = o.nonExistentProperty;
f(1,2,3); // will trigger o.__noSuchMethod__('f', [1,2,3])

I'd say that's better and more consistent with existing Javascript coding style than the existing __noSuchMethod__ behavior, which would set 'f' to undefined.

Another benefit of the proxy-based __noSuchMethod__ implementation is that the cost of reifying missing methods is only paid by objects that explicitly "buy into" the feature by inheriting from the special proxy. So: objects that _don't_ inherit from that special proxy don't incur the overhead associated with __noSuchMethod__.

Also in that thread, I point out that, given Javascript's "invoke = get+call" behavior, a better hook for Javascript altogether would be "__noSuchProperty__(name)" which by default returns "undefined", but could be overridden to return, for instance, a function(...args) {} to emulate a missing method.

That said, there have been other occasions where we'd wish we could distinguish get from invoke, as pointed out in the last paragraph of https://mail.mozilla.org/pipermail/es-discuss/2011-November/018771.html for example.

Andrea Giammarchi said...

sorry Tom but once again this is WRONG for so many reasons:

var f = o.nonExistentProperty;
f(1,2,3); // will trigger o.__noSuchMethod__('f', [1,2,3])

this is not the expected behavior in ECMAScript itself so why __noSuchMethod__ should behave like that?

That uses an implicit bind that does not make sense ... __noSuchMethod__ cannot be bound because the method does NOT exist ... I can believe this is obvious for me only ...

Andrea Giammarchi said...

once again:

var f = o.nonExistentProperty;

is A GETTER, not a __noSuchMethod__


this is a __noSuchMethod__ call
o.nonExistentProperty();

there is no ambiguity on the syntax AND the behavior

Tom Van Cutsem said...

If "nonExistentProperty" is missing on a _normal_ object o then the above is indeed unexpected. But it does not make much sense to refer to the "expected behavior of ECMAScript" here, since your proposal, i.e. that:

o.nonExistentProperty(); // will work, but
var f = o.nonExistentProperty;
f(); // will crash because f is undefined

is _also_ unexpected behavior, because in standard ECMAScript method invocation and funarg extraction+apply are supposedly equivalent.

Andrea Giammarchi said...

as memo: broken invariants I will walk through next post ...

Andrea Giammarchi said...

in standard ECMAScript method invocation and funarg extraction+apply are supposedly equivalent

there is NO METHOD TO INVOKE, the fallback is called __noSuchMethod__ !!!

Andrea Giammarchi said...

which means you are extracting NOTHING and you cannot invoke NOTHING, also NOTHING won't be a method anymore.

I feel I am explaining the obvious ( at least to me ) ... which part of NO-SUCH-METHOD is ambiguous is still a mystery to me.

You address that? You pass through a getter, there's nothing? Then NOTHING will be

Brendan Eich said...

First, I'm the guy who created __noSuchMethod__ in SpiderMonkey way back in 2003 (see https://bugzilla.mozilla.org/show_bug.cgi?id=196097), so don't take my name only in vain.

Second, the invariant at stake is not o.m() <=> o.m.apply(o), it's let f = o.m; f.apply(o). Lots of JS extracts methods as funargs and passes them around with their |this| parameter. Or the methods close over self and don't use |this|.

Third, we can bring back noSuchMethod as an unstratified, private-named trap, akin to iterator. That is a proposal we've deferred for now, in order to see exactly how hard it is to use proxies.

The answer is "not hard". Tom's direct proxies work enables his MethodSink proxy to be a prototype object, providing a noSuchMethod trap.

Instead of beefing about lack of __noSuchMethod__ exactly as I implemented it in 2003 (warts and all, which other browsers will not accept), how about we try out the direct proxy solution and see how that goes? We could even put it in the standard library, so it would be "batteries included".

/be

Andrea Giammarchi said...

as Dmitry said ... Tom trap breaks almost everything so once again Brendan, you know what you did there, and that was great.

If we address a non existent property/method, nobody in this world should expect that to work ... is that easy, really

Dmitry A. Soshnikov said...

My new summary on the issue: https://gist.github.com/1481018