introduction of statefull functions (inspired by Scala language) - advanced specification of "operator()" for arbitrary objects
The nice thing about @@symbol-named methods is that we don't have to extend the Proxy API for them. They're just method calls.
This is why there's no hasInstance Proxy trap. instanceof
basically
boils down to a method call, and we already have Proxy traps for
method calls: get
and apply
.
It's a good thing, too, because adding a new Proxy trap is a last resort. Just from a conceptual standpoint, the less complicated objects are, the better; just because there are 14 fundamental operations on objects doesn't mean we're eager to add more. But there's also an inherent compatibility issue. Whenever you add a new trap, the deal is, all existing Proxy handlers were written without consideration for the new operation. So old code, used in combination with the new language feature, would tend to break.
I would like to point out that this particular example could be done in terms of existing proxies now:
var sttFunc = new Proxy({}, {
get(target, key) {
console.log("get: [", key, "]");
return key;
},
set(target, key, value) {
console.log("set: [", key, "] = ", val);
return true; // success
}
});
// Usage:
sttFunc["key"] = "value"; // set: [key] = value
sttFunc["key"]; // get: [key]
Fist of all little bit more details. Here @@apply
and @@update
proposed
like some "fallback scenario" for "()" for non function objects (and not
Proxy objects) - generally for objects that do not support "()"
"natively".
(1)
Currently js has "()"
operation (lets call it "_=_()"
) but has not
"()="
operation (lets call it "_()=_"
). Also operation "()" currently
applied only to a functions, but not applied to the arbitrary objects (even
when I create Proxy from arbitrary object and trying to implement "()" in
Proxy, it still not work since target object was not a function, as for me
it is more bug then feature:) ). So it supposed that if obj
is not a
function, then operation "()"
goes to fallback scenario and try to use
obj[@@apply]
as an implementation of "()"
operation for this obj
object. But if obj
object is Proxy object, than it obtains this operation
as "direct" call of apply
handler method (bypassing fallback to
obj[@@apply]
) . So obj[@@apply]
should be used for "()" operation in
some similar way as vlaueOf
and toString
are used by "+"
operation -
when object itself natively support this operation (like Number, String,
etc does) the no fallback scenarios are used - "+"
operation just do its
native implementation, but when object do not natively support "+"
(case
of arbitrary object, not Number, not String, ...) then some additional
fallback actions happens (with calling valueOf
/ toString
).
So here is the same idea - if obj
is a function (or Proxy object) no
fallback actions should occur - operation "()" should be done "natively".
if object not a function neither a Proxy object, then fallback to
obj[@@apply]
should occur (and signature of this obj[@@apply]
-method
assumed to be the same as in proxy handler apply
method
(function(target, thisArg, argumentsList) {}
) - so it should obtain more
information then just a list of an arguments it also should obtain target
and thisArg
where target
should be bound to obj
and thisArg
should
be also properly passed, if it was owner.obj(arg)
then thisArg
should
be bound to owner
(NOT to obj
)).
(2)
For "()="
operation (obj(..args) = val
) currently no implementation
present (this construction unconditionally cause compile time error). But
if we assume that it really present/implemented, and name it as update
(like it was done in Scala), then update
and apply
in Proxy handler
should be in the same relationship as get
and set
, and once again, it
should be assumed that Prxoy object may "natively" implement update
operation, and "fallback scenario" related to obj[@@update]
should NOT
happen for Proxy objects.
So more strict definition off behavior will look like this:
Considering owner.obj(..args) = val
case:
- If
owner.obj
is NOT a Proxy object (do not support_()=_
operation "natively") then this should be evaluated to
owner.obj[@@update](/*target:*/ owner.obj, /*thisArg:*/ owner, args)
- If
owner.obj
is Proxy object (and that's why do support_()=_
operation "natively") then underling assignment should refer to Proxy object handlerupdate
operation with same arguments as above, if proxy handler do not supportupdate
implementation, than it can fallback totarget
implantation of_()=_
operation (and apply this rule recursively but this time fortarget
)
Considering this thoughts particular answer to your post will be:
... and we already have Proxy traps for method calls:
get
andapply
if obj
is Proxy object, it assumed that it implements "_=_()
" and
"_()=_"
operations "natively" so it should not use obj[@@apply]
and
obj[@@update]
methods (and that is why get
should not trigger), instead
"_=_()
" and "_()=_"
operations should be directly forwarded to proxy
handler (apply
and update
method accordingly) ; obj[@@apply]
and
obj[@@update]
methods should be used only for objects which are both not
js Function neither js Proxy object.
Also proposed update
handler method is more like counterpart of apply
,
like set
proxy handler method is counterpart to get
proxy handler
method.
But there's also an inherent compatibility issue. Whenever you add a new
trap, the deal is, all existing Proxy handlers were written without consideration for the new operation. So old code, used in combination with the new language feature, would tend to break.
In case if old code was used, then it should mean that no update
method
present in proxy handler and so that obj(...args) = val
will fallback to
target(...args) = val
(by actual result of execution); if target
object
was also defined/constructed by old code, then construct obj(...args) = val
will lead to the same error as target(...args) = val
does (since old
code will not implement it for target
too), so nothing will change for
old code in this case; if old code of proxy will be applied for target
object from "new code" (that occasionally happen to implement "_()=_"
operation some how), then obj(...args) = val
will succeed transparently
and has same result as target(...args) = val
. And yes, "old code" proxy
handler will not intercept this situation, and that means that all handlers
that currently intercept all operations over object will became less
"universal". But this doesn't mean that "purely old" code will be broken in
any way by introduction of this feature.
I would like to point out that this particular example could be done in
terms of existing proxies ...
Yes, for this simplistic case that you properly mentioned, operation []
is enough, but when i writing that example I rather keep in mind js Map
and object keys. So the goal is also simplified syntax of accessing and
mutating maps, like:
var map = new Map();
var key = {foo: "bar"};
var val = {some: "value"};
map(key) = val; // same as map.set(key, val) if js has "`_()=_`" operation
console.assert(map(key) === val); // same as (map.get(key) === val)
map.delete(key);
console.assert(map(key) == null);
If "_()=_"
operation will be available in language, then anyone will be
able to implement his own MultiDimMap
multidimensional map (on top of
existing single-dimensional map), with usage similar to following
var map = new MultiDimMap();
var key0 = {foo: "bar"};
var key1 = {foo1: "bar1"};
var val = {some: "value"};
map(key0, key1) = val;
console.assert(map(key0, key1) === val);
map.delete(key0, key1);
console.assert(map(key0, key1) == null);
Also to provide more complete information about current state of similar
feature in other languages, it would be good to take a look on C#
indexers. It also
provides extensive possibility to implement "_[]=_"
operation with
arbitrary number of arguments, so in case of C# expressions like
obj.idxr[arg1, arg2, ... , argN] = val
is pretty valid (if obj.idxr
indexer properly implemented in obj
). But this feature looks for me more
ambiguous with current implementation of Proxy object (more specifically
with current definition of set
and get
methods of proxy handler).
In particular set
states signature:
set: function(target, property, value, receiver)
but if we will have a list of arguments (like obj[arg1, ... , argN]
) then
what should be passed to property
argument, if it will be property == [arg1, ... , argN]
then it looks like OK, but if we pass only one
argument, like obj[Array.from([arg1, ... , argN])]
what should be in
property
, of course it should be rather property == [ [arg1, ... , argN] ]
but still it looks little bit ambiguous.
So for me Scala approach for
"indexing" with "()"
/ "()="
operations looks more nice.
(More information about Scala approach to apply
/update
- "()"
/
"()="
can be found at
www.scala-academy.com/tutorials/scala-apply-update-methods-tutorial,
also about maps:
www.safaribooksonline.com/library/view/scala-cookbook/9781449340292/ch11s15.html,
docs.scala-lang.org/overviews/collections/maps#operations-in-class-mutablemap
)
It would be nice to "bring sense" to expressions like
obj(a1, ... , aN) = val
. (likeobj(x) = y
) In Scala langue it defined in pretty clear and simple way:Of course this applied only to that cases when obj was not defined like method (in case of regular methods
mth(args) = val
will cause compile time error). So in Scala even arrays and maps are accessed and updated using "()" operator(see www.scala-lang.org/api/2.10.0/index.html#scala.Array , docs.scala-lang.org/overviews/collections/maps#operations-in-class-mutablemap , etc)
So the proposals are:
(1) to introduce symbols like
@@apply
and@@update
with very similar to Scala meaning (for cases whenobj.sttFunc
is not a function)(2) to extend Prxoy object specification to support update action along with apply action:
I would like to write something similar to
And then run it as following