On 11.10.2010 18:18, Tom Van Cutsem wrote:
This is odd, I didn't see the original message you were replying to.
Yes, really odd. It's not the first time when the archive-server looses
letters.
OK, here is original letter sent before (the main question is not in
"magic methods" directly on object, it's just a academic curiosity, but
the main question is -- how in one /get/ method of the handler to manage
missing properties and missing methods?) :
-------- Original message -----
Hi,
I continue experiments with proxies (the first one was delegation-based
mixins). Currently (only for academic curiosity) I've implemented
objects with "magic hooks" which are defined "directly" on the objects.
Actually, it's just a "proxied proxies", and there is no much sense in
it (except maybe that such objects syntactically are shorter than real
proxies), but repeat, the reason is -- only an academic interest of how
do Proxy objects work. The script is here: http://gist.github.com/617338
But this letter is not about the script (the script is just a reason),
but about Proxy objects themselves. I'd like to clarify some features.
First, unfortunately, because of stratification of
/Proxy.create(handler, proto)/ and /Proxy.createFunction(handler,
callTrap, constructTrap)/ I can't manage in my "sugar" /typeof/ and
/[[Class]]/ of a proxy object (depending on presence of /__call__/ or
/__construct__/ -- in one case /typeof/ should result "function", in
other case, if I later remove both /__call__/ and /__construct__/, it'd
nice to have "object"). Yes, it's quit clear that it's related with
/Function.prototype/ which is (should be?) set as a prototype of the
function proxy. This question is not so essential, because I understand
that the proxy strawman's idea assumes that a user will work with
proxies, but not with directly defined (not stratified from an object)
meta-methods, so I just mention this fact (that it turned out
inconvenient for my script) :) Although, possibly someone will want to
use this Python's "fancy" /__name__/s "sugar".
The most interesting question for me is mentioned several times (the
last one was by Brendan in his slides from the lates JS Conf) "get +
defining Fn" vs. invoke trap.
Yes, there are some cons: a non-existing method can be extracted as a
funarg for later use. It also do not break invariant with call/apply
methods applied for the method:
var foo = proxy.nonExistingMethod;
Thus, /foo/ is a real function and also /proxy.nonExistingMethod()/ is
the same as /proxy.nonExistingMethod.call(undefined)/.
However, I don't see how can I implement a generic /get/ method in the
proxy handler? I.e. I'd like to trap at the same time both: reading
simple properties and also invocation of methods. In my script these
are: /__get__/ (traps reading of all properties), /__noSuchProperty__/
(traps reading only of missing properties) and /__noSuchMethod__/ (traps
methods invocations).
Of course, /get/ method of the handler may return (possibly creating
every time new) a function and then we have
/__noSuchMethod__/implementation. That's OK (having this approach we
have "funargs + call/apply" scheme). But unfortunately, we can't
distinguish what contains a call-site:
Is it a property reading:
foo.bar
or possible invocation:
foo.bar()
How should /get/ trap of the proxy be implemented to support both:
/__noSuchProperty__/ and /__noSuchMethod__/ ? This is the main question.
Seems, there is no obvious way.
On the other hand, regating "funargs + call/apply" scheme: if the /get/
trap returns each time a new function, then === invariant is broken.
That means it should return some predefined function from available for
handling function-table (i.e. if name is "foo" -- return table.foo,
etc). It of course can dynamically define the function and save it in
the table. And actually to keep === invariant, it should do so.
Dmitry.
-------- End of original message -----
To respond to the question you posed in the "syntax for efficient
traits"-thread:
Yes, I do. The latest is "magic methods" directly on objects
(possibly you saw -- http://gist.github.com/617338; I don't know
what again with es-archive server, but it doesn't display the
lettter which I send about "get+fn vs. invoke"
--https://mail.mozilla.org/pipermail/es-discuss/2010-October/thread.html --
there only answer on the letter with correcting the typo).
Hmm, your API defines all the handler methods directly on the object,
thus explicitly ignoring the stratification afforded by the original
API. Granted, your implementation filters out magic properties in
appropriate places, so they won't show up in e.g. for-in loops, but
the magic methods are still easily accessible directly from the
object. I understand that people just want to write:
myObject.__get__ = function() { ... }
instead of:
var handler = Proxy.noopHandler(myOriginalObject);
handler.get = function() { ... }
myObject = Proxy.create(handler, Object.getPrototypeOf(myOriginalObject));
I agree that the current API is too bulky if all you want is
__noSuchMethod__, but I'd much rather see a wrapper library that
allows me to write something like this:
var [myObject, handler] = wrap(myOriginalObject);
handler.get = function() { ... }
or just:
var myObject = wrap(myOriginalObject, { get: function() { ... } })
This still keeps all the meta-functionality of an object in a separate
object. Code that only has access to "myObject" can't mess around with
its meta-level behavior. 'magic' methods don't prevent a client from
inadvertently overriding an object's traps.
Yes, everything correct with stratification of the meta- and normal-
levels; I know that this is the main reason. As I said (and repeat it
again) -- my implementation with __Ugly?PythonsNames__ is just an
academic curiosity -- to play with proxies. And was playing with them, I
backed to the ideological dilemma which was mentioned also several
times: "get + fn vs. noSuchMethod/invoke". This is what Brendan
mentioned before and also in the last slides of the recent JS Conf (I
saw only slides, I didn't see yet video of Brendan's talk -- maybe he
already answered this question? However, I didn't find the answer in
slides).
The main (42? ;)) question is: how having /one get/ method in a proxy's
handler to handle both cases of a call-site -- a /property reading/ and
a /method invocation/ ?
foo.bar
and
foo.bar()
Currently I understand, that implementation of the __noSuchMethod__
described in the strawman article is just wrong -- because /get/ method
of the handler /always/ returns a /function/. That means, /foo.bar/ - is
a function, /foo.baz/, /foo.whatTheHack/ - is also a function. How a
user will differentiate accesses to a non-function properties to a
function properties?
Returning a function approach has advantages:
1. returned method can be extracted as a functional object (and e.g.
passed as a funarg);
2. we have working call/apply invariant, i.e. foo.bar.apply(...) is
foo.bar(...)
But what if the user wants to trap get for non-existing /properties/ and
non-existing /functions/? Here e.g. /get/ function from my implementation:
/**
* generic [[Get]]
*/
handler.get = function (r, name) {
// __get__ hook
if ("__get__" in object) {
object.__get__(name);
}
// if a property is not found
if ("__noSuchProperty__" in object && !(name in object)) {
return object.__noSuchProperty__(name);
}
// here I don't know how to handle a method call-site
return object[name];
};
foo.bar; // __get__ bar
foo.nonExisting; // __get__ nonExisting, __noSuchProperty__ nonExisting
foo.baz(); // __get__ baz
foo.nonExisting(); // *HOW CAN I MANAGE IT?*
I don't know what a call-site contains -- a function invocation or a
property reading, so I can't in one get return a function or a property.
There can be /invoke/ trap which traps /all/ invocations:
foo.bar(); // __invoke__ bar
foo.nonExisting(); // __invoke__ bar
But then it will be hard to distinguish it from /get/ which returns a
function.
On the other hand, there can be not /invoke/, but /noSuchMethod/
(/methodMissing/, /doesNotUnderstan/ -- call it as you wish) -- /exactly
method/, i.e. a /TypeError "not a function"/. Then we can combine both:
get+fn and noSuchMethod:
get: function (r, name) {
if (name == "bar") {
return function () {};
}
return object[name];
}
then we have:
proxy.bar; // function
proxy.baz; // object.baz
proxy.bar(); // OK -- get + fn -> call
proxy.nonExisting() // fail, get+undefined -> call: TypeError "not a
function"
And exactly the last case can be trapped with noSuchMethod. I can even
implement it in my script (e.g. catching onError with debugger service,
or even simple window.onerror -- though will be hard with arguments).
But it can be additional trap for a proxy:
noSuchMethod: function (name, args) {
console.log(name, args);
}
then:
proxy.nonExisting(1, 2, 3); // get "nonExisting" -> call-site contains
call expression -> noSuchMethod("nonExisting", [1, 2, 3])
The issues are also known:
proxy.nonExisting(1, 2, 3); is not the same as
proxy.nonExisting.apply(null, [1, 2, 3]) and we can't extract
proxy.nonExisting as a function ('cause obviously it's not a function).
But what is more convenient for a user -- to catch such methods or to
keep invariants with apply/funargs? Also, as I mentioned, even if it is
done via get, then /to keep === invariant/, get should return always the
same missing function for a property (which means to keep some dispatch
table). So, anyway, some invariants will be lost anyway (or will be
complex/inconvenient in implementation) and it's needed to choose what
is better for a user. I think it will be good to have additionally for
proxies noSuchMethod trap.
Dmitry.
Maybe you can answer on this question regarding proxies? How can
we handler in one get of a proxy handler both cases of a call-site:
foo.bar
and
foo.bar()
?
Having invoke, we handler foo.bar(). However, the case when
the get returns should look (unfortunatelly?) like (foo.bar)().
Also of course foo.bar does not extracted as a functional object
(including invariants foo.bar.call/apply). But I think, is it the
case that user wants to used them exactly as funargs or he wants
more to handle just access (exactly calling of) to non-existing
methods? P.S.: to avoid off-topic regarding this topic devoted to
traits/mixins, it's better to answer to that trhead -- "get+fn vs.
invoke".
In short: the 'get' trap can't distinguish the above two cases. This
is a pity, and I agree it would be useful for 'get' to have that
information sometimes. There has previously been talk on this list of
parameterizing 'get' with an additional flag to detect get+call vs
invoke, but it turned out that not all implementations would be able
to support it:
<https://mail.mozilla.org/pipermail/es-discuss/2010-May/011062.html>
In retrospect it may not be so bad that this feature isn't supported.
I can imagine it could lead people to write code like:
get: function(receiver, name, args) {
// suppose that args === undefined implies that the property was
only queried, not invoked
if (args === undefined || args.length === 0) {
return 'a';
} else {
...
}
}
So that obj.name <http://obj.name> and obj.name <http://obj.name>()
would both return 'a'. This could lead to a lot of confusion, since
it's hard to quantify what "obj.name <http://obj.name>" denotes. If
it's supposed to be a method, then obj.name <http://obj.name> should
return a function. If it's supposed to be a getter, then obj.name
<http://obj.name>() should call the thing returned by the getter
(which, in this case, should raise a type error since strings are not
callable)
Cheers,
Tom
2010/10/9 Dmitry A. Soshnikov <[email protected]
<mailto:[email protected]>>
On 10.10.2010 0:44, Dmitry A. Soshnikov wrote:
Yes, there are some cons: a non-existing method can be extracted
as a funarg for later use. It also do not break invariant with
call/apply methods applied for the method:
Sorry, typo, not cons, but pros of course.
Dmitry.
_______________________________________________
es-discuss mailing list
[email protected] <mailto:[email protected]>
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss