Overriding Map/etc with get/set hooks?

Allen Wirfs-Brock allen at wirfs-brock.com
Wed May 22 01:48:27 PDT 2013


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?  

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.

In the ES6 built-ins where there is a need to do a quick behavoral check for a "kind" of object we defined a method such as @@isRegExp which is used to enable various string methods to recognize multiple different concrete implementations of "RegExp-like" objects.  You could imagine us providing a@@isMap but we currently have no strong use cases for it.

> 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.

> 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?  First let mean define what "generic map method" would mean to mean.  It is a method that does not directly depend upon the internal [[MapData]] state.  It only accesses that state indirectly via get/set/has etc.

Currently there are no generic Map.prototype methods.  They all are directly tied to the [[MapData]] representation for some specific reason. 

At this point in time it isn't obvious what generic map methods would be useful to add in the future.

> 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.

> 
> All of these follow from the basic statement that *this is a map*,
> because it clearly is.  It's limited to string keys and values, but
> that's just some coercion rules.  Every single operation defined on
> Maps currently is meaningful and useful for this object, and virtually
> any method you can imagine adding to Maps in the future is also
> meaningful and useful.
> 
> Ducktyping this as a Map is terrible, because it breaks #2/3/4, and
> arguably #1 depending on what you consider an "appropriately
> idiomatic" way of asking if something is Map-like.
> 
> Just subclassing Maps and providing my own get/set functions is not as
> terrible, but still bad for a few reasons: I need to study the ES spec
> very carefully to figure out which operations are "basic" (interact
> directly with [[MapData]] rather than going through existing
> operations) and redefine all of them, and anyone who ever does
> something like Map.prototype.set.call(myObj, ...) will break the
> invariants I need maintained, with mysterious and undefined results.

There are none.  All the current Map methods are intentionally dependent upon [[MapData]]

> 
> This should be super-simple - I just want to supply a custom object as
> the [[MapData]] which can intercept sets so it can coerce the values
> beforehand.  In every other way, this is a Map.

Note [[MapData]] isn't just a specification abstraction over a data store.  It also abstracts all the access patterns of that data store.

Allen




More information about the es-discuss mailing list