noSuchMethod: "funargs" + "invoke-only-phantoms"
Agreed there are use-cases for second-class methods, according to style and taste.
The impetus for noSuchMethod when I implemented it in 2003 was to support the Smalltalk-based TIBET framework of Bill Edney and Scott Shattuck. They religiously use a Smalltalk style of JS so do not feel any second-class pain.
Other styles of JS would definitely feel pain. One size does not fit all.
This is why rejecting an invoke trap is not a matter of black and white, IMHO -- it's simply a desire to reduce complexity and see how the result can be used by a library (a standard one, even) to implement something like noSuchMethod.
Yep, no doubt, first-class "missed" methods win -- again, because the programmer can and has the complete right (by just looking at one line of a code) to rewrite simple invoke to `apply' (she don't have to think whether it's a virtual method or not).
The only thing I wanted is to reduce broken consequences. Well, or at least to be aware about them ;)
Dmitry.
you don't use apply randomly, you use apply for methods or getters knowing there is a function there.
noSuchMethod is about NOT HAVING A FUNCTION there and if the property is not defined apply should fail as well as obj.undefined.apply would
I still do not understand why we keep mixing up getters with noSuchMethod behavior which is: 1. a "method" and not a property invocation ( no obj.inexistent.apply BUT ONLY obj.inexistent() OR objinexistent ) 2. unaddressable since a property that has not been define will always be addressed as undefined ( or the proto chain value ) 3. nothing to defer, lazy call, pass through, etc etc ... once again, noSuchMethod SHOULD cover 1 case, and 1 case only
obj.iDoNotExistHowCanAnyoneReferAtMeThen();
Rules behind the scene, described already in my post:
Syntax: object.methodName(); // inline invokaction, NO EXCEPTIONS TO THIS SINGLE CASE Procedure:
- check if object has a property called "methodName" 1.1 yes, go on and throw an error if it is not callable 1.2 no, check if the property has a getter 1.2.1 yes, go on and throw an error if it is not callable 1.2.2 no, check if the object has a "noSuchMethod" fallback 1.2.2.1 yes, invoke that callback via cb.call(object, "noSuchMethod", arguments) 1.2.2.2 no, throw an error "undefined is not a method"
Is above logic really that hard to implement/understand? I don't think so but it looks like it's me only.
The described behavior as it is is never ambiguous so what is the problem exactly?
Practical example
var o = Object.defineProperty({}, "test", { get: function () { return this.alias; } }); o.alias = function () { alert(this.message); }; o.message = "hello";
o.toString(); // proto chain o.alias(); // property as method o.test(); // getter o.noTest(); // noSuchMethod o.test.call(o); // getter o.noTest.call(o);// undefined is not a function
Best
sorry for the typo, this point was
1.2.2.1 yes, invoke that callback via cb.call(object, "methodName", arguments)
I don't think SHOUTING helps your argument.
The key point is whether and how any user of an abstraction can know that a given method is first- or second-class. Some frameworks such as TIBET use second-class methods only.
Others (E4X, ECMA-357 comes to mind -- users did and do trip over its invoke-only methods) could segregate and document methods as to which can be extracted and called later with an appropriate |this|, and which are invoke-only.
But arguing your conclusion that noSuchMethod is only about the latter case of NOT HAVING A FUNCTION doesn't help. It's a circular argument.
Abstractions of the O-O varieties have different philosophies about methods, but when applied to JS, the cross-browser libraries do not say "you have to know if this method is real or virtual." People have not built much on noSuchMethod since it's only in SpiderMonkey. We lack experience in-the-large with it.
On this basis, I'm still happy to see method-missing traps implementable on top of direct proxies. Users will soon be able to experiment with such libraries in SpiderMonkey and V8. Then we can see what we've learned. If you are right, it'll be trivial to standardize one of these libraries, or even make a private-named unstratified noSuchMethod trap.
if both V8 and SpiderMonkey will follow that logic to trap invoke-only method, I'll do my best to promote and explain how it works and why addressing anyhow does not make sense since there is nothing to address being inexistent.
That case would be covered eventually by no such property but this one specially is trivial with Proxies, the method isn't.
Te shouting is me being lazy with bold and stars trying to underlying common misconception I have read around about what "problem" noSuchMethod should bring, in my opinion non of them as long as the behavior is well defined and clear which is, to me, the case since ever.
Good to know that at least SpiderMonkey is not planning to drop it
Best
On 17.12.2011 9:56, Andrea Giammarchi wrote:
if both V8 and SpiderMonkey will follow that logic to trap invoke-only method, I'll do my best to promote and explain how it works and why addressing anyhow does not make sense since there is nothing to address being inexistent.
It was my main thoughts and arguments through that "never-ending initial thread". And this position of course makes perfect sense.
However, it may also be broken until we don't have the answer to the following question:
Why does a programmer have an error in the following transformation of a code?
Was:
lib.toRussianName("Andrea"); // ??????
Became:
var toRussianName = lib.toRussianName; toRussianName.apply(null, ["Andrea", "Alex"]); // error, "toRussianName" is undefined
If you can explain it to the programmer that she should first go to the documentation and to see that methods such as `to<Country>Name' are
virtual, but not real and then to use all such cases with caution, then this is OK. This is actually the only way to solve this issue -- to tell the programmer "go to the documentation and check which virtual, i.e. invoke-only methods you have".
In other case, you can't explain to her why she, being in JS, can't transform this line of a code into the second one. Can she do this? Does she have rights/license for this? Of course she can, of course she does.
But in all other broken cases -- I already said, I agree that this implementation also breaks some logic. And in the same manner you'll have to explain to the programmer why if she:
delete lib.toRussianName;
then it still:
typeof lib.toRussianName; // "function", Trollface ;D
So, the only answer on this topic is should be the answer on "what is less painful and less critical" in this case? To explain one broken invariants or others? Once we have answered this question, we may easily implement any from this design decisions.
Dmitry.
Hello,
from my point the scenario that:
delete lib.toRussianName;
then it still:
typeof lib.toRussianName; // "function", Trollface ;D
is much more acceptable (it happens everyday - there are non-writable properties everywhere), than the scenario where I cannot transform
lib.toRussianName("Andrea"); // Андрей
into
var toRussianName = lib.toRussianName;
toRussianName.apply(null, ["Andrea", "Alex"]); // error, "toRussianName"
is undefined
That's why I proposed first-class phantoms in gist: beasts that typeof to "phantom", ToBoolean to false, ToNumber to 0, are wrapping a function, [[Call]] calls that function, [[Get]] and [[Prototype]] delegates to that function ([[Class]] is questionable) and rest does not work ([[Set]] silently fails etc.). And the ability to return them from any code, not just proxies.
Dmitry, addressing a trap fallback is not a good idea plus the average JS coder rarely does it ... said that, the moment you are using a method you already know this exists so you already know the documentation ( or part of it ) so I don't see much hurt there.
Moreover, the addressing problem is common for all self bound methods and the usage of call and apply
var o {getStuff:function(){return o.stuff}}; // or o.getStuff = o.getStuff.bind(o);
Whoever gonna address "getStuff" will have unexpected results with any context different from o so, again, whoever is using a method must know at least basis of the methods.
Methods that fallbacks through a trap as noSuchMethod is should not be documented as methods because inexistent, these should rather be documented under the "magic behavior" explaining in the very first how it works and why.
I don't see many troubles into an invoke-only fallback, I see troubles or limits without this possibility. The proxy trap is not even an option to me, not to simulate proper noSuchMethod behavior where again, typeof o.nonExistentMethod should be "undefined", accordingly with proto and getter over that property.
Are these missing? Cool, there is no such method or property with that name so "undefined" and nothing else.
Ideally, we could find a better way to define objects that implements noSuchMethod interface so that developers can be aware in advance of potential mistakes
and about last point, maybe objects that implements noSuchMethod should return something like "unknown" via typeof ... just saying, and simply to differentiate these objects from others where noSuchMethod is not in place.
On 17.12.2011 17:21, Herby Vojčík wrote:
Hello,
from my point the scenario that:
delete lib.toRussianName;
then it still:
typeof lib.toRussianName; // "function", Trollface ;D
is much more acceptable (it happens everyday - there are non-writable properties everywhere),
Yes, good point on non-configurable properties. So, we may justify this broken invariant with `delete' which becomes not so broken. it's good. Agreed.
than the scenario where I cannot transform
lib.toRussianName("Andrea"); // Андрей
into
var toRussianName = lib.toRussianName; toRussianName.apply(null, ["Andrea", "Alex"]); // error, "toRussianName" is undefined
That's why I proposed first-class phantoms in gist: beasts that typeof to "phantom", ToBoolean to false, ToNumber to 0, are wrapping a function, [[Call]] calls that function, [[Get]] and [[Prototype]] delegates to that function ([[Class]] is questionable) and rest does not work ([[Set]] silently fails etc.). And the ability to return them from any code, not just proxies.
This is by the way, may also be an interesting idea. You mean "phantom" is the function returned from `get' or it for "non-existing" method in case of noSuchMethod is used?
Dmitry.
On 17.12.2011 18:08, Andrea Giammarchi wrote:
Dmitry, addressing a trap fallback is not a good idea plus the average JS coder rarely does it ... said that, the moment you are using a method you already know this exists so you already know the documentation ( or part of it ) so I don't see much hurt there.
That's it. In designing a language it's better not to build the decisions based on "rarely" reasons. Yes, of course we should consider percentage of usage of this or that feature, so basically it's good to analyze whether is feature is "rare" or "frequent". But in general, we can't predict 100% will the method be used in some different ways or not -- especially if the user may use it in this way in JS.
Once again, in PHP and others it's much easier to implement things like
__call', etc (until 5.3 it even didn't have closures). In languages like Ruby it's also easier to implement it, since
foo.bar' is already
a method call (or sending a message). There is only method calls in
Ruby, there are no properties. This is why `method_missing' is logical
there.
In JS, as well as in Python, there is no method calls, but there is
getting property values and already after that provide some operations
on them (in case if the value of the property is a function, you may
call it). This is, by the way, why Python also doesn't support separated
__nomethod__' magic name. In Python, as well as in JS proxies, you have to return an "activator" function from
getattr'.
Moreover, the addressing problem is common for all self bound methods and the usage of call and apply
var o {getStuff:function(){return o.stuff}}; // or o.getStuff = o.getStuff.bind(o);
Whoever gonna address "getStuff" will have unexpected results with any context different from o so, again, whoever is using a method must know at least basis of the methods.
Yes, this is fair enough. If the user extracts the function for further
applies, she should know whether a function is generic to be able apply
it for different this' values. OTOH, she can do this only for optimizations.
toString.call' is faster than
`Object.prototype.toString.call', so she just saves it to the variable
and calls.
Methods that fallbacks through a trap as noSuchMethod is should not be documented as methods because inexistent, these should rather be documented under the "magic behavior" explaining in the very first how it works and why.
Yes, as I said, I see this way as the only existing to solve the issue -- to mention in the documentation, that methods such as `find_by_<fieldName>' are magic and that we can't extract them or apply
in different contexts. If this is acceptable, then this is OK. Then we force user to know it and to follow this rule. OTOH, it may be good.
I don't see many troubles into an invoke-only fallback, I see troubles or limits without this possibility. The proxy trap is not even an option to me, not to simulate proper noSuchMethod behavior where again, typeof o.nonExistentMethod should be "undefined", accordingly with proto and getter over that property.
Yep, this is also true, and I mentioned it several times in that long thread.
Are these missing? Cool, there is no such method or property with that name so "undefined" and nothing else.
Ideally, we could find a better way to define objects that implements noSuchMethod interface so that developers can be aware in advance of potential mistakes
This is what nearly Tom proposes with inheriting a special (proxy) object. I.e. some object which inherit it will have this unstratified noSuchMethod, placed directly on the object (still via proxies, with all broken invariants applied), and some do not. OTOH, he also proposed to be `Object.prototype' as such a proxy. It can be a good idea, though will decrease efficiency.
Dmitry.
That's why I proposed first-class phantoms in gist: beasts that typeof to "phantom", ToBoolean to false, ToNumber to 0, are wrapping a function, [[Call]] calls that function, [[Get]] and [[Prototype]] delegates to that function ([[Class]] is questionable) and rest does not work ([[Set]] silently fails etc.). And the ability to return them from any code, not just proxies.
This is by the way, may also be an interesting idea. You mean "phantom" is the function returned from `get' or it for "non-existing" method in case of noSuchMethod is used?
Returned from `get'. Or wherever else, too, if you feel, but get trap of a proxy is one of the obvious uses.
From: "Dmitry Soshnikov" <dmitry.soshnikov at gmail.com>
On 17.12.2011 17:21, Herby Vojčík wrote:
Hello,
from my point the scenario that:
delete lib.toRussianName;
then it still:
typeof lib.toRussianName; // "function", Trollface ;D
is much more acceptable (it happens everyday - there are non-writable properties everywhere),
Yes, good point on non-configurable properties. So, we may justify this broken invariant with `delete' which becomes not so broken. it's good.
Not so fast: the result of the delete expression differs in the non-configurable case (false) from the no-such-property case (true).
On 18.12.2011 2:51, Brendan Eich wrote:
From: "Dmitry Soshnikov"<dmitry.soshnikov at gmail.com>
On 17.12.2011 17:21, Herby Vojčík wrote:
Hello,
from my point the scenario that:
delete lib.toRussianName; then it still: typeof lib.toRussianName; // "function", Trollface ;D
is much more acceptable (it happens everyday - there are non-writable properties everywhere), Yes, good point on non-configurable properties. So, we may justify this broken invariant with `delete' which becomes not so broken. it's good. Not so fast: the result of the delete expression differs in the non-configurable case (false) from the no-such-property case (true).
Right, we have to adjust delete' trap as well. And, if we treat these virtual methods as non-configurable, the to adjust also
getOwnPropertyDescriptor' hook too.
I edited the implementation: gist.github.com/1481018#L229
Still, some problems appear after that:
-
if we return descriptor at
getOwnPropertyDescriptor', potentially this means that the user can change some attributes via
Object.defineProperty'. E.g. to setenumerable' to false, and we have to handle it in
defineProperty' hook. OTOH, if our property is non-configurable, it means user can't change some attribute values in `Object.defineProperty'. -
@Tom: Found bugs in DirectProxies.js
- Properties created via assignment gets
false' value for descriptor attributes; should be true. E.g. foo.bar = 10, where
foo' is direct proxy, makes bar non-configurable - Can't return descriptor with `configurable: false' for non-existing property; get: "cannot report a non-configurable descriptor for non-existent property" But we need it in case virtual methods
- Properties created via assignment gets
Dmitry.
- @Tom: Found bugs in DirectProxies.js
Thanks for reporting, but I don't think these are bugs:
- Properties created via assignment gets
false' value for descriptor attributes; should be true. E.g. foo.bar = 10, where
foo' is direct proxy, makes bar non-configurable
I can't reproduce this. Both in tracemonkey and ff8 I get the following:
js> var t = {}
js> var p = Proxy(t, {})
js> p.x = 1
1 js> Object.getOwnPropertyDescriptor(t, 'x') ({value:1, writable:true, enumerable:true, configurable:true}) js> Object.getOwnPropertyDescriptor(p, 'x') ({value:1, writable:true, enumerable:true, configurable:true})
There is, however, a TM-specific bug that I suspect may be the cause of your observed "non-configurable by default" behavior: < bugzilla.mozilla.org/show_bug.cgi?id=601329>
- Can't return descriptor with `configurable: false' for non-existing
property; get: "cannot report a non-configurable descriptor for non-existent property" But we need it in case virtual methods
You can (and probably should) advertise a virtual method as configurable:true.
The proxy throws this exception because, for properties that do not exist on the wrapped target, it cannot guarantee that they will always be non-configurable. For example, if your proxy handler now says that "foo" is {value:10, configurable:false}, nothing stops your proxy handler from later claiming that "foo" is {value:0, configurable:true}.
This is almost the exact behavior I was talking about ...
Object.withNoSuchMethod = function withNoSuchMethod(obj, noSuchMethod) { var cachedInvokes = {}; return Proxy.create({ get: function (receiver, name) { return name in obj ? obj[name] : cachedInvokes.hasOwnProperty(name) ? cachedInvokes[name] : cachedInvokes[name] = function () { if(this === receiver) return noSuchMethod.call(obj, name, arguments); throw new Error("undefined is not a function"); } ; } }); };
var p = Object.withNoSuchMethod({/generic object/}, function (prop, args) { alert([prop, args.length]); });
(p.test)(1, 2, 3); var test = p.test; test.call(p, 1, 2, 3); test.apply(p, [1, 2, 3]); p.test(1, 2, 3); test.bind(p)(1, 2, 3);
test(1, 2, 3); // undefined is not a function test.call(null, 1, 2, 3); // undefined is not a function
On 20.12.2011 17:00, Tom Van Cutsem wrote:
- @Tom: Found bugs in DirectProxies.js
Thanks for reporting, but I don't think these are bugs:
1. Properties created via assignment gets `false' value for descriptor attributes; should be true. E.g. foo.bar = 10, where `foo' is direct proxy, makes bar non-configurable
I can't reproduce this. Both in tracemonkey and ff8 I get the following:
js> var t = {} js> var p = Proxy(t, {}) js> p.x = 1 1 js> Object.getOwnPropertyDescriptor(t, 'x') ({value:1, writable:true, enumerable:true, configurable:true}) js> Object.getOwnPropertyDescriptor(p, 'x') ({value:1, writable:true, enumerable:true, configurable:true}) There is, however, a TM-specific bug that I suspect may be the cause of your observed "non-configurable by default" behavior: bugzilla.mozilla.org/show_bug.cgi?id=601329
Hm, I can't reproduce it either now (latest release Firefox, WinXP). Perhaps it was really the bug you noticed with my Firefox at work.
2. Can't return descriptor with `configurable: false' for non-existing property; get: "cannot report a non-configurable descriptor for non-existent property" But we need it in case virtual methods
You can (and probably should) advertise a virtual method as configurable:true.
I can only configurable: true', but as we said, to fix broken
delete'
operator, we need to treat these virtual properties as non-configurable.
We logically return false' in
delete' trap and have to adjust
`getOwnPropertyDescriptor' as well.
Well, how to say "have to..."? It just seems the most logical justification of this broken invariant -- to treat them as non-configurable. Yes, and by the way, also as non-enumerable since they will not appear in the for-in.
The proxy throws this exception because, for properties that do not exist on the wrapped target, it cannot guarantee that they will always be non-configurable.
What to do with these virtual properties then? It would be good to have them {configurable: false, enumerable: false, writable: true, value: noSuchMethodActivator}
For example, if your proxy handler now says that "foo" is {value:10, configurable:false}, nothing stops your proxy handler from later claiming that "foo" is {value:0, configurable:true}.
Wait, but we can do the same for any property, including existing. Or am I missing something?
Funny implementation :P You did it vice-versa: functional objects do work, but simple calls do not :D
No, not good to depend on `this' value and non-Reference type.
Dmitry.
2011/12/21 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>
On 20.12.2011 17:00, Tom Van Cutsem wrote:
You can (and probably should) advertise a virtual method as configurable:true.
I can only
configurable: true', but as we said, to fix broken
delete' operator, we need to treat these virtual properties as non-configurable. We logicallyreturn false' in
delete' trap and have to adjust `getOwnPropertyDescriptor' as well.
This seems to be a persistent source of confusion about invariants in ES5: The fact that a property is configurable:true implies that it can be successfully deleted is not an ECMAScript invariant. The ES5 spec only requires that configurable:false implies that the property cannot successfully be deleted.
The difference is subtle, but important. It would not surprise me if there exist host objects that advertise their properties as configurable:true, don't complain when you try to delete them, yet when you query the properties again, they're still there. This is entirely within the bounds of the ES5 spec. MarkM once explained the difference well: < esdiscuss/2011-May/014150>
(sorry, was in a small vacation, can't answer earlier)
Yes, I'm perfectly aware about what host objects may do (there is even wide-spread phrase in JS community: "do not trust to host objects, they can do almost anything").
From this viewpoint, if to treat these proxies as host objects, then in
fact any of discussed broken invariants can be justified. But, I wanted to consider them from the view of casual JS objects.
Regarding the "subtle difference", if to consider casual JS objects, then no, 8.12.7 (es5.github.com/#x8.12.7), step 3.a cleary states that the property is removed.
Dmitry.
Here is the analysis of current "noSuchMethod" situation implemented via proxies.
I summarized that never-ending thread from 2010 ( esdiscuss/2010-October/011929), since guys in JS community started to ask why proxies don't support noSuchMethod.
It's written as a small article in a view of JS-code: gist.github.com/1481018
Is there something to add? To change probably in the current Tom's proposal? Etc.
P.S.: while I was writing the article, I started more to agree on importance of the "extracted funargs" in this case, however the "invoke-only-phantom" methods still and also (as it turns out) are needed to users and required by them.