Overriding Map/etc with get/set hooks?

classic Classic list List threaded Threaded
88 messages Options
12345
Reply | Threaded
Open this post in threaded view
|

Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
For the CSS Variables spec I need to define an object with arbitrary
string keys, with the initial set determined by the custom properties
set in the style rule, and on modification I need to coerce the
provided key to a string, and then go mutate the style rule
accordingly.  When the style rule is mutated to add/remove custom
properties, I also need to mutate the exposed keys on the object.  (In
other words, this object has a bidirectional link with a style rule;
it just exposes a more convenient and specialized interface for custom
properties specifically.)

Right now I'm defining this via the WebIDL getter/setter/etc hooks,
which ends up defining an "object map".  This is bad practice, though,
because anything set on the prototype chain will show up as a
(non-own) key, potentially causing confusion.

I'd like to convert this over to an ES Map, as that avoids the above
issue and gets me all the Map extras for free, which is nice.
However, I don't think it's currently possible to do what I need.

Is it possible add appropriate hooks to the ES spec to let me define
the [[MapData]] via a spec, rather than as an initially-empty list of
tuples that get/set unconditionally read from?  I need to be able to
define in spec-ese that the [[MapData]] tuples consist of some list of
data from a style rule, and that whenever a value gets set, I first
coerce the key to a string, and then go mutate the style rule instead
of the [[MapData]] (it then picks up the mutated data by virtue of
being defined by the style rule).

(One way to do this today is to subclass Map and provide my own
get/set/etc. functions, but I need to override a potentially-open set
(anything that doesn't directly lean on my overridden functions), and
it doesn't prevent people from directly twiddling my [[MapData]] by
calling Map.prototype.set.call() on my object.)

Alternately: Proxies?  Is that possible?  What benefits/drawbacks come
from that?

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: Overriding Map/etc with get/set hooks?

Domenic Denicola-2
Seems like this isn't really a Map? It'd be pretty confusing for something to pretend to be a Map but act in such coercive and side-effecty ways. Better to just obey the Map structural type, perhaps, to give people an interface they're used to while not pretending to be something you're not?

> -----Original Message-----
> From: [hidden email] [mailto:es-discuss-
> [hidden email]] On Behalf Of Tab Atkins Jr.
> Sent: Monday, May 20, 2013 22:06
> To: es-discuss
> Subject: Overriding Map/etc with get/set hooks?
>
> For the CSS Variables spec I need to define an object with arbitrary string
> keys, with the initial set determined by the custom properties set in the style
> rule, and on modification I need to coerce the provided key to a string, and
> then go mutate the style rule accordingly.  When the style rule is mutated to
> add/remove custom properties, I also need to mutate the exposed keys on
> the object.  (In other words, this object has a bidirectional link with a style
> rule; it just exposes a more convenient and specialized interface for custom
> properties specifically.)
>
> Right now I'm defining this via the WebIDL getter/setter/etc hooks, which
> ends up defining an "object map".  This is bad practice, though, because
> anything set on the prototype chain will show up as a
> (non-own) key, potentially causing confusion.
>
> I'd like to convert this over to an ES Map, as that avoids the above issue and
> gets me all the Map extras for free, which is nice.
> However, I don't think it's currently possible to do what I need.
>
> Is it possible add appropriate hooks to the ES spec to let me define the
> [[MapData]] via a spec, rather than as an initially-empty list of tuples that
> get/set unconditionally read from?  I need to be able to define in spec-ese
> that the [[MapData]] tuples consist of some list of data from a style rule, and
> that whenever a value gets set, I first coerce the key to a string, and then go
> mutate the style rule instead of the [[MapData]] (it then picks up the
> mutated data by virtue of being defined by the style rule).
>
> (One way to do this today is to subclass Map and provide my own
> get/set/etc. functions, but I need to override a potentially-open set
> (anything that doesn't directly lean on my overridden functions), and it
> doesn't prevent people from directly twiddling my [[MapData]] by calling
> Map.prototype.set.call() on my object.)
>
> Alternately: Proxies?  Is that possible?  What benefits/drawbacks come from
> that?
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
On Mon, May 20, 2013 at 7:55 PM, Domenic Denicola
<[hidden email]> wrote:
> Seems like this isn't really a Map? It'd be pretty confusing for something to pretend to be a Map but act in such coercive and side-effecty ways. Better to just obey the Map structural type, perhaps, to give people an interface they're used to while not pretending to be something you're not?

Oh, no, "obeying the Map structural type" is much worse than even my
bad solution of subclassing Map in my OP. It means I have to define
bespoke versions of *all* the Map functions myself, rather than just
the "basic" ones (and keep adding to the interface as ES adds to Map),
functions added by the author to Map.prototype don't show up, it
doesn't type as a Map with the ES methods of typing, etc.

It's clearly a Map - it's a set of key/value tuples, which you can
create/read/update/delete/iterate, exactly like the vanilla Map.  The
only difference is that its [[MapData]], rather than being a
freshly-created independent empty list upon creation, is a
spec-defined list that reflects the data from another object.  This
doesn't interfere with its operation as a Map, it just makes the
get/set operations slightly more complex than they are for vanilla
maps.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Domenic Denicola-2
Oh, I must have misread your original message. I thought it did not allow storing non-string keys. If it can allow storing any kind of key, like a Map, and it's just the initial data you're referring to, then maybe it is a Map. As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.

On May 21, 2013, at 1:16, "Tab Atkins Jr." <[hidden email]> wrote:

> On Mon, May 20, 2013 at 7:55 PM, Domenic Denicola
> <[hidden email]> wrote:
>> Seems like this isn't really a Map? It'd be pretty confusing for something to pretend to be a Map but act in such coercive and side-effecty ways. Better to just obey the Map structural type, perhaps, to give people an interface they're used to while not pretending to be something you're not?
>
> Oh, no, "obeying the Map structural type" is much worse than even my
> bad solution of subclassing Map in my OP. It means I have to define
> bespoke versions of *all* the Map functions myself, rather than just
> the "basic" ones (and keep adding to the interface as ES adds to Map),
> functions added by the author to Map.prototype don't show up, it
> doesn't type as a Map with the ES methods of typing, etc.
>
> It's clearly a Map - it's a set of key/value tuples, which you can
> create/read/update/delete/iterate, exactly like the vanilla Map.  The
> only difference is that its [[MapData]], rather than being a
> freshly-created independent empty list upon creation, is a
> spec-defined list that reflects the data from another object.  This
> doesn't interfere with its operation as a Map, it just makes the
> get/set operations slightly more complex than they are for vanilla
> maps.
>
> ~TJ
>

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Anne van Kesteren
On Tue, May 21, 2013 at 6:20 AM, Domenic Denicola
<[hidden email]> wrote:
> As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.

I think it would have to subclass those methods by using toString() on
the argument passed. It seems that would still be Map-like though.


--
http://annevankesteren.nl/
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Domenic Denicola-2
On May 21, 2013, at 1:23, "Anne van Kesteren" <[hidden email]> wrote:

On Tue, May 21, 2013 at 6:20 AM, Domenic Denicola
<[hidden email]> wrote:
As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.

I think it would have to subclass those methods by using toString() on
the argument passed. It seems that would still be Map-like though.


Hmm, so that invariant wouldn't hold? I assume `has` would be similarly broken? Seems… not so  Map like, besides perhaps having some operations with the same name as Map's.


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
In reply to this post by Domenic Denicola-2
On Mon, May 20, 2013 at 10:20 PM, Domenic Denicola
<[hidden email]> wrote:
> Oh, I must have misread your original message. I thought it did not allow storing non-string keys. If it can allow storing any kind of key, like a Map, and it's just the initial data you're referring to, then maybe it is a Map. As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.

It's a string-keyed map, but that just means that you toString
everything, as Anne says.  Your contract is still maintained, assuming
a non-degenerate toString.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
In reply to this post by Domenic Denicola-2
On Mon, May 20, 2013 at 10:27 PM, Domenic Denicola
<[hidden email]> wrote:
> Hmm, so that invariant wouldn't hold? I assume `has` would be similarly
> broken? Seems… not so  Map like, besides perhaps having some operations with
> the same name as Map's.
>
> Relevant: http://en.wikipedia.org/wiki/Liskov_substitution_principle

Let's ignore Liskov; I don't really care about subclassing, not least
because being strictly Liskov-pure makes most subclasses invalid.

The things I care about:

* when someone asks "is this Map-like?" in an appropriately idiomatic
JS way, they get a "yes" answer.
* when someone adds a new function to Maps in an appropriately
idiomatic JS way, the method also applies to this object
* when JS expands the set of built-in methods for Map, it also gets
applied to this object without me having to update my spec
* for all the existing Map methods, I get identical/equivalent methods
without having to manually redefine every single one of them

All of these are easy to do if this is just a Map (or has Map on its
prototype chain), but with a custom [[MapData]] whose behavior is
defined by my spec.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Domenic Denicola-2
In reply to this post by Tab Atkins Jr.


On May 21, 2013, at 1:28, "Tab Atkins Jr." <[hidden email]> wrote:

> On Mon, May 20, 2013 at 10:20 PM, Domenic Denicola
> Is<[hidden email]> wrote:
>> Oh, I must have misread your original message. I thought it did not allow storing non-string keys. If it can allow storing any kind of key, like a Map, and it's just the initial data you're referring to, then maybe it is a Map. As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.
>
> It's a string-keyed map, but that just means that you toString
> everything, as Anne says.  Your contract is still maintained, assuming
> a non-degenerate toString.

Right, I guess it's this contract that gets broken: `x !== y` implies `map.set(x, 1); map.set(y, 2); map.get(x) === 1; map.get(y) === 2`.
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
On Mon, May 20, 2013 at 10:33 PM, Domenic Denicola
<[hidden email]> wrote:

> On May 21, 2013, at 1:28, "Tab Atkins Jr." <[hidden email]> wrote:
>> On Mon, May 20, 2013 at 10:20 PM, Domenic Denicola
>> Is<[hidden email]> wrote:
>>> Oh, I must have misread your original message. I thought it did not allow storing non-string keys. If it can allow storing any kind of key, like a Map, and it's just the initial data you're referring to, then maybe it is a Map. As long as the contract that `map.set(x, y); map.get(x) === y` works for any `x` and `y`, then you're probably fine.
>>
>> It's a string-keyed map, but that just means that you toString
>> everything, as Anne says.  Your contract is still maintained, assuming
>> a non-degenerate toString.
>
> Right, I guess it's this contract that gets broken: `x !== y` implies `map.set(x, 1); map.set(y, 2); map.get(x) === 1; map.get(y) === 2`.

Yes, because it's a string map rather than an object map.  But that
difference doesn't justify breaking all the qualities I listed in my
previous message.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Sam Tobin-Hochstadt
In reply to this post by Tab Atkins Jr.
On Mon, May 20, 2013 at 10:32 PM, Tab Atkins Jr. <[hidden email]> wrote:
>
> All of these are easy to do if this is just a Map (or has Map on its
> prototype chain), but with a custom [[MapData]] whose behavior is
> defined by my spec.

Would another way to think about this be as a regular plain-old Map,
but which is updated imperatively by the environment sometimes, and is
also read by the environment?  Or does changing the map also change
some part of the style of the page _immediately_, rather than at the
end of the turn?

Sam
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
On Mon, May 20, 2013 at 11:10 PM, Sam Tobin-Hochstadt <[hidden email]> wrote:
> On Mon, May 20, 2013 at 10:32 PM, Tab Atkins Jr. <[hidden email]> wrote:
>> All of these are easy to do if this is just a Map (or has Map on its
>> prototype chain), but with a custom [[MapData]] whose behavior is
>> defined by my spec.
>
> Would another way to think about this be as a regular plain-old Map,
> but which is updated imperatively by the environment sometimes, and is
> also read by the environment?

Sure, that's also valid.

> Or does changing the map also change
> some part of the style of the page _immediately_, rather than at the
> end of the turn?

It would be best if it happened immediately.  There's no
performance-related reason not to make it sync, and high potential for
confusion if you use one method to write it, another tool uses the
other method to read it, and they don't sync up.  It's just a
(necessary) convenience API for reading/writing the CSS properties
directly.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Sam Tobin-Hochstadt
On Mon, May 20, 2013 at 11:17 PM, Tab Atkins Jr. <[hidden email]> wrote:

> On Mon, May 20, 2013 at 11:10 PM, Sam Tobin-Hochstadt <[hidden email]> wrote:
>> On Mon, May 20, 2013 at 10:32 PM, Tab Atkins Jr. <[hidden email]> wrote:
>>> All of these are easy to do if this is just a Map (or has Map on its
>>> prototype chain), but with a custom [[MapData]] whose behavior is
>>> defined by my spec.
>>
>> Would another way to think about this be as a regular plain-old Map,
>> but which is updated imperatively by the environment sometimes, and is
>> also read by the environment?
>
> Sure, that's also valid.
>
>> Or does changing the map also change
>> some part of the style of the page _immediately_, rather than at the
>> end of the turn?
>
> It would be best if it happened immediately.  There's no
> performance-related reason not to make it sync, and high potential for
> confusion if you use one method to write it, another tool uses the
> other method to read it, and they don't sync up.  It's just a
> (necessary) convenience API for reading/writing the CSS properties
> directly.

Is it possible for the environment to change the Map during the turn?

IOW, is this always true?

m.set("x", 1);
assert(m.get("x") === 1);

Sam
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Anne van Kesteren
On Tue, May 21, 2013 at 7:24 AM, Sam Tobin-Hochstadt <[hidden email]> wrote:
> Is it possible for the environment to change the Map during the turn?
>
> IOW, is this always true?
>
> m.set("x", 1);
> assert(m.get("x") === 1);

If we ignore the toString() and other validation, yes. But note that
setting will directly be observable through getComputedStyle.
Object.observe does not work for this, just like it does not work for
URLQuery (which we discussed a while back).


--
http://annevankesteren.nl/
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Sam Tobin-Hochstadt
On Mon, May 20, 2013 at 11:32 PM, Anne van Kesteren <[hidden email]> wrote:

> On Tue, May 21, 2013 at 7:24 AM, Sam Tobin-Hochstadt <[hidden email]> wrote:
>> Is it possible for the environment to change the Map during the turn?
>>
>> IOW, is this always true?
>>
>> m.set("x", 1);
>> assert(m.get("x") === 1);
>
> If we ignore the toString() and other validation, yes. But note that
> setting will directly be observable through getComputedStyle.
> Object.observe does not work for this, just like it does not work for
> URLQuery (which we discussed a while back).

If that's all the case, then why do you need any fancy spec
methodology? You have a Map, which is consulted by, and mutated by,
the environment. When you first create this Map, you add a bunch of
stuff to it.  And that's it, AFAICT.

Sam
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Tab Atkins Jr.
In reply to this post by Sam Tobin-Hochstadt
On Mon, May 20, 2013 at 11:24 PM, Sam Tobin-Hochstadt <[hidden email]> wrote:
> Is it possible for the environment to change the Map during the turn?
>
> IOW, is this always true?
>
> m.set("x", 1);
> assert(m.get("x") === 1);

Your example is not a restatement of your question.

Yes, the "environment" can change the map during the turn.  But the
map isn't affected by arbitrary things in the "environment" - it just
shares its internal data with another object which is also user
read/writeable.  The only way the map can change without a .set() call
is if your code does some CSSOM manipulation that changes the custom
properties in the associated style rule.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Sam Tobin-Hochstadt
On Mon, May 20, 2013 at 11:58 PM, Tab Atkins Jr. <[hidden email]> wrote:

> On Mon, May 20, 2013 at 11:24 PM, Sam Tobin-Hochstadt <[hidden email]> wrote:
>> Is it possible for the environment to change the Map during the turn?
>>
>> IOW, is this always true?
>>
>> m.set("x", 1);
>> assert(m.get("x") === 1);
>
> Your example is not a restatement of your question.
>
> Yes, the "environment" can change the map during the turn.  But the
> map isn't affected by arbitrary things in the "environment" - it just
> shares its internal data with another object which is also user
> read/writeable.  The only way the map can change without a .set() call
> is if your code does some CSSOM manipulation that changes the custom
> properties in the associated style rule.

Ok, that's what I thought was going on.  We can make some other
function calls which might mutate the Map, just as with any other Map
that we get from someone else. In that case, my response to Anne
stands -- why is any spec magic needed at all?

Sam
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Anne van Kesteren
On Tue, May 21, 2013 at 8:11 AM, Sam Tobin-Hochstadt <[hidden email]> wrote:
> Ok, that's what I thought was going on.  We can make some other
> function calls which might mutate the Map, just as with any other Map
> that we get from someone else. In that case, my response to Anne
> stands -- why is any spec magic needed at all?

Well basically, you want an object that's a Map for all intents and
purposes, but does a couple of things differently. So e.g. you need a
custom set and all Map operations that use set need to use that new
set. Tab's original message explains that however as far as I can tell
so it's not entirely clear to me what we're missing.


--
http://annevankesteren.nl/
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Sam Tobin-Hochstadt
On Tue, May 21, 2013 at 2:52 AM, Anne van Kesteren <[hidden email]> wrote:

> On Tue, May 21, 2013 at 8:11 AM, Sam Tobin-Hochstadt <[hidden email]> wrote:
>> Ok, that's what I thought was going on.  We can make some other
>> function calls which might mutate the Map, just as with any other Map
>> that we get from someone else. In that case, my response to Anne
>> stands -- why is any spec magic needed at all?
>
> Well basically, you want an object that's a Map for all intents and
> purposes, but does a couple of things differently. So e.g. you need a
> custom set and all Map operations that use set need to use that new
> set. Tab's original message explains that however as far as I can tell
> so it's not entirely clear to me what we're missing.

No, you don't need to do anything differently.  Conceptually, there
are three things you need:

1. When the Map is created, before it's handed to the program, some
items are added.
2. Some platform operations also change this map in addition to doing
the other things they do.
3. Some other set of platform operations consult this map when doing
their other work.

Obviously, this is the spec perspective; an implementation could have
some magic version of the Map that does the update of the internal
platform state eagerly when map.set() is called.

Sam
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Overriding Map/etc with get/set hooks?

Anne van Kesteren
On Tue, May 21, 2013 at 11:01 AM, Sam Tobin-Hochstadt <[hidden email]> wrote:

> No, you don't need to do anything differently.  Conceptually, there
> are three things you need:
>
> 1. When the Map is created, before it's handed to the program, some
> items are added.
> 2. Some platform operations also change this map in addition to doing
> the other things they do.
> 3. Some other set of platform operations consult this map when doing
> their other work.
>
> Obviously, this is the spec perspective; an implementation could have
> some magic version of the Map that does the update of the internal
> platform state eagerly when map.set() is called.

How does that ensure that e.g.

  map.set("var-" + somethingNotAllowedByCSS, "test")

throws / is ignored (forgot what the desired semantic is)? Or

  map.set("var-test", {toString:function(){return"test")})
  map.get("var-test")

returns "test"?


--
http://annevankesteren.nl/
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
12345