[Bug 20019] Support subclassing ES6 Map

# Mark S. Miller (13 years ago)

[+es-discuss]

Speaking only for myself at this point -- I do not recall MultiMap previously being suggested to the committee.

I think adding a MultiMap API to ES7 is a good idea. Neither Map nor MultiMap should be a subclass of the other, since neither is an LSP subtype of the other.

Since Map and Set will be in ES6 and MultiMap is trivially implementable from these, we can wait until we see some experimental implementations before standardizing. Hence the ES7 target.

The issue of subclassing built-in types in general is interesting and important. Whatever we do for this in general, we should not need to make any special case for Map and MultiMap. In general, new built-in abstractions should act as much possible as if they were implemented by an ES6 class and exported by an ES6 module. (Likewise for old build-in abstractions, but less will likely be possible for these.)

# Kris Kowal (13 years ago)

On Tue, Nov 20, 2012 at 10:57 AM, Mark S. Miller <erights at google.com> wrote:

Since Map and Set will be in ES6 and MultiMap is trivially implementable from these, we can wait until we see some experimental implementations before standardizing. Hence the ES7 target.

Here’s my experimental implementation, in terms of my Map shim.

kriskowal/collections/blob/master/multi-map.js

# Tab Atkins Jr. (13 years ago)

On Tue, Nov 20, 2012 at 10:57 AM, Mark S. Miller <erights at google.com> wrote:

[+es-discuss]

Speaking only for myself at this point -- I do not recall MultiMap previously being suggested to the committee.

I think adding a MultiMap API to ES7 is a good idea. Neither Map nor MultiMap should be a subclass of the other, since neither is an LSP subtype of the other.

When properly designed, as long as you interact with it only through Map methods, a MultiMap can be an LSP subtype of Map.

This means that get() should grab the first value, if one exists, or undefined otherwise, while a separate method (getAll?) returns a (potentially empty) list of all values. set() should take only a single value, and wipe out all other values currently attached to the key. (Or, be n-ary and replace values at corresponding indexes. Given get()'s behavior, this is behaviorally indistinguishable from a plain Map, even if multiple values already exist on a key.) delete() should wipe out all the values for a given key. (Plus, perhaps have a 2-arg version which specifies a value to kill.)

The tricky part is dealing with .size and the iterator methods. You need .size to reflect the number of keys, not pairs, to be consistent with Map. But then .size doesn't match the length of the iterators returned by .items() or .values(), unless both of these are changed to only return the first value by default. (They can't return an array of values, because that's not what Map does.)

Since Map and Set will be in ES6 and MultiMap is trivially implementable from these, we can wait until we see some experimental implementations before standardizing. Hence the ES7 target.

Sure. It just means that we'll have legacy in the DOM already as well.

The issue of subclassing built-in types in general is interesting and important. Whatever we do for this in general, we should not need to make any special case for Map and MultiMap. In general, new built-in abstractions should act as much possible as if they were implemented by an ES6 class and exported by an ES6 module. (Likewise for old build-in abstractions, but less will likely be possible for these.)

Agreed.

# Mark S. Miller (13 years ago)

On Tue, Nov 20, 2012 at 12:30 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Tue, Nov 20, 2012 at 10:57 AM, Mark S. Miller <erights at google.com> wrote:

[+es-discuss]

Speaking only for myself at this point -- I do not recall MultiMap previously being suggested to the committee.

I think adding a MultiMap API to ES7 is a good idea. Neither Map nor MultiMap should be a subclass of the other, since neither is an LSP subtype of the other.

When properly designed, as long as you interact with it only through Map methods, a MultiMap can be an LSP subtype of Map.

This means that get() should grab the first value, if one exists, or undefined otherwise, while a separate method (getAll?) returns a (potentially empty) list of all values. set() should take only a single value, and wipe out all other values currently attached to the key. (Or, be n-ary and replace values at corresponding indexes. Given get()'s behavior, this is behaviorally indistinguishable from a plain Map, even if multiple values already exist on a key.) delete() should wipe out all the values for a given key. (Plus, perhaps have a 2-arg version which specifies a value to kill.)

The tricky part is dealing with .size and the iterator methods. You need .size to reflect the number of keys, not pairs, to be consistent with Map. But then .size doesn't match the length of the iterators returned by .items() or .values(), unless both of these are changed to only return the first value by default. (They can't return an array of values, because that's not what Map does.)

This is all very tricky and you may be able to make it work. But why? Do you anticipate passing a multimap into a place that expects a map? For these use cases, do you expect that the passer of the multimap reliably intends to effectively pass only these "first" mappings per key to the receiver? As I write this, it all seems crazy and unreliable, and not a pattern we should encourage. Let's keep Map and MultiMap as distinct types, and standardize only Map, WeakMap, and Set this round.

# Tab Atkins Jr. (13 years ago)

On Tue, Nov 20, 2012 at 1:06 PM, Mark S. Miller <erights at google.com> wrote:

This is all very tricky and you may be able to make it work. But why? Do you anticipate passing a multimap into a place that expects a map? For these use cases, do you expect that the passer of the multimap reliably intends to effectively pass only these "first" mappings per key to the receiver? As I write this, it all seems crazy and unreliable, and not a pattern we should encourage. Let's keep Map and MultiMap as distinct types, and standardize only Map, WeakMap, and Set this round.

I think it's reasonable in some cases to use a MultiMap as a plain Map

  • for example, in URLQuery, you might not want to encode any duplicated keys. In that case, it's perfectly fine to have it act like a normal Map.

However, it may be better to just let MultiMaps have a method to cast themselves into Maps. After all, there are already two distinct types of MultiMaps - ordered (Array) and unordered (Set) - the latter can't be treated as a Map since it lacks the concept of a "first" value.

A more important quality is probably just being iterable, which both Map and MultiMap would have in the same way, so that may be sufficient.

# Brendan Eich (13 years ago)

Mark S. Miller wrote:

Since Map and Set will be in ES6 and MultiMap is trivially implementable from these, we can wait until we see some experimental implementations before standardizing. Hence the ES7 target.

Same goes for Bag, only moreso, eh? ;-)

# Andreas Rossberg (13 years ago)

On 20 November 2012 21:30, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Tue, Nov 20, 2012 at 10:57 AM, Mark S. Miller <erights at google.com> wrote:

I think adding a MultiMap API to ES7 is a good idea. Neither Map nor MultiMap should be a subclass of the other, since neither is an LSP subtype of the other.

When properly designed, as long as you interact with it only through Map methods, a MultiMap can be an LSP subtype of Map.

[...]

The tricky part is dealing with .size and the iterator methods. You need .size to reflect the number of keys, not pairs, to be consistent with Map. But then .size doesn't match the length of the iterators returned by .items() or .values(), unless both of these are changed to only return the first value by default. (They can't return an array of values, because that's not what Map does.)

If the multi map iterator returns the same key multiple times it already breaks the map contract. So you would need a separate iteration method for that. At that point, as Mark says, it is not clear what the benefit is.

The proper approach would be to identify a common super class that identifies the commonalities. You could try to come up with a hierarchy of concepts, like they did for C++0X before it got downsized. But lacking types that hardly seems useful for JavaScript.

# Axel Rauschmayer (13 years ago)

Map and Set are the bare collection necessities and let us avoid the hazards of using objects as maps. Beyond that, I would want to see a holistically designed collections API. Tackling equality seems important: How do value objects fit into the picture? Will there be a per-type protocol? Or an operator (or similar) that one extends?

With modules and classes in ES6, we only need someone willing and able to implement such an API as an external library. As far as I’m aware, no such libraries exist. Which is surprising – IIRC several such libraries have been written for Java.

Axel