Overriding Map/etc with get/set hooks?
Tab Atkins Jr.
jackalmage at gmail.com
Wed May 22 11:21:41 PDT 2013
On Wed, May 22, 2013 at 1:48 AM, Allen Wirfs-Brock
<allen at wirfs-brock.com> wrote:
> On May 21, 2013, at 6:42 PM, Tab Atkins Jr. wrote:
>> On Tue, May 21, 2013 at 4:19 AM, Brendan Eich <brendan at mozilla.com> wrote:
>>> David Bruant wrote:
>>>> This description ("arbitrary string keys", "bidirectional link with style
>>>> rule") suggests that you want a proxy (canonical values are in the style
>>>> rule, the proxy is just a façade with a bit of validation/coercion logic).
>>>> That's the sort of use case they've been introduced for.
>>> I don't see why a proxy is required if the API is get/set/has (like Map's).
>>> We're not making properties appear without knowing their names, so this is
>>> not a proxy use-case on its face.
>>> Of course, coercing key type makes the API not Map. So if the
>>> bi-directionality is important, this would be a custom Map-like class.
>>> So Tab: why do you want to abuse Map instead of make a custom class?
>> Restating from my earlier post:
>> 1. when someone asks "is this Map-like?" in an appropriately idiomatic
>> JS way, they get a "yes" answer.
> First you have to define what "Map-like" means. Is it supporting get/set/has? Is it any object that conforms to Sam's "check" invariant?
A store of key/value pairs. That's the definition of a map.
> There is currently no specified way to ask if something is "Map-like" because we don't know. BTW, in the school of dynamic language OO development that I come from it is considered an "anti-pattern" to ask if something is "a Foo" or is "Foo-like". If your (informal) contract says that you require that somebody pass you a Foo-like object, just assume that they did. If they don't some sort of error is likely to occur soon enough. You have to bake sure that you actively mantain essential internal invariants but there is no real need to sweat about higher-level "type check" around the edges.
I know that prototype checking isn't a great way to check if you
satisfy some contract. On the other hand, ES has no other way to do
so, and especially no other way to say "this method should work for
everything that satisfies the 'map' contract".
>> 2. when someone adds a new function to Maps in an appropriately
>> idiomatic JS way, the method also applies to this object
> You mean add methods to Map.prototype? Modify other people's (including built-in) prototypes is rather frowned upon. Map is designed to be subclassed. If you what a new kind of map with additional methods the appropriate way to do that would be to add those methods of a subclass and use instances of the subclass instead of Map.
Frowned upon, but super useful and widely done. Say I wanted to add a
'reduce' method to Map (same as Array#reduce, but passes in the key as
an argument too). There's nothing wrong with applying this to *every*
Map, so I'd add it to Map.prototype. Subtyping Map means that I don't
get this for free; I have to pass every outside Map through a
converter function, for no real reason except theoretical purity.
>> 3. 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.
> Why do you think this will happen?
Do you really think tc39 will never define more Map methods? More Set
methods? Given the history of every other built-in class, it seems
certain that we'll gradually expand the surface of Map and Set.
>> 4. for all the existing Map methods, I get identical/equivalent methods
>> without having to manually redefine every single one of them
> Except that all of the existing methods are dependent upon the specific [[MapData]] representation. If you want to use a different representation of the data for a new kind of map you are going to have to over-ride all of the existing methods anyway.
> You could define Map using a two level implementation strategy where, for example, the "get" method called perhaps an @@get method and where all representational knowledge would be in the @@ methods. However, you you still have to over-ride the same number of @@ methods so I don't see a major gain in that approach.
[[MapData]] is just a list of tuples. All I need for my spec (and for
future specs that want something Map-like) is the ability to define my
own list of tuples (linked to another object), and the ability to
intercept sets/deletes so I can do coercion and adjustment of other
Unless I'm completely missing something, there are at most 4 "basic"
things to override, the same 4 things that you override to make an
"object map": create/read/update/delete.
Is it really more complicated than this? If so, how?
More information about the es-discuss