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.objis 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.objis Proxy object (and that's why do support_()=_operation "natively") then underling assignment should refer to Proxy object handlerupdateoperation with same arguments as above, if proxy handler do not supportupdateimplementation, than it can fallback totargetimplantation 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:
getandapply
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) = valwill 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
@@applyand@@updatewith very similar to Scala meaning (for cases whenobj.sttFuncis 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
It would be nice to "bring sense" to expressions like "obj(a1, ... , aN) = val". (like "obj(x) = y") In Scala langue it defined in pretty clear and simple way: "obj(a1, ... , aN)" <==> "obj.apply(a1, ... , aN)" "obj(a1, ... , an) = *val*" <==> "obj.update(a1, ... , aN, *val*)" 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 js( arr[index] = val ) <==> scala( arr(index) = val ) (see http://www.scala-lang.org/api/2.10.0/index.html#scala.Array , http://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 when 'obj.sttFunc' is not a function) "obj.sttFunc(a1, ... , aN) = *val*" ==> "obj.sttFunc[Symbol.update] (obj.sttFunc, obj, [a1, ... , aN], *val*)" "obj.sttFunc(a1, ... , aN)" ==> "obj.sttFunc[Symbol.apply] (obj.sttFunc, obj, [a1, ... , aN])" *(2)* to extend *Prxoy* object specification to support *update* action along with *apply* action: I would like to write something similar to var target = { jsApply: function(key){ console.log("apply: ", key); return key; }, jsUpdate: function(key, val){ console.log("update: (", key, ") = ", val); return val; } }; var sttFunc = new Proxy(target, { apply: function(target, thisArg, argumentsList) { target.jsApply.apply(thisArg, argumentsList); }, update: function(target, thisArg, argumentsList, assignValue) { target.jsApply.apply(thisArg, Array.from(argumentsList).concat([assignValue])); } }); And then run it as following > sttFunc ("key") = "value"; update: ( key ) = value > sttFunc ("key"); apply: key "key" -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160512/fef01dd4/attachment.html>