-
Notifications
You must be signed in to change notification settings - Fork 17
don't prefer Object over Map #33
Comments
I can’t speak for anyone else ofc, but imo destructuring will be wanted there vast majority of the time, and i do not prefer working with a Map unless i have object keys, which for me is a rarity - objects continue to be the “proper” keyed collections for most of my use cases. |
See earlier discussion in #3. Note that most of the reasons listed in that MDN article to prefer Maps over objects aren't relevant here: we're producing a null-prototype object, so there's no accidental keys; performance is very likely to be better for objects unless you're mutating the result a bunch, which is a weird thing to do; etc. And conversely, there are strong reasons to prefer objects (when they are suitable, i.e. you need only string and symbol keys), which include a.) they're more convenient, since you can access properties directly, b.) the rest of the language works with them, as in JSON.stringify and destructuring, and c.) this matches the ecosystem precedent. I think there is very good reason for both to exist. And I think it makes sense for the shorter |
Wow. Okay. I see that a lot of this has already been discussed. I believe Lodash and the like introduced JavaScript developers to a I do think that a I am not aware of data that says that the vast majority of use cases for e.g. // using a Map
Array.from(array.groupBy(…), ([k, v]) => …);
// using an Object
Array.from(Object.entries(array.groupBy(…)), ([k, v]) => …)); I still think using a Thank you. |
A Map is more general purpose, but it's also less convenient to use in common cases. And I think it makes sense to have the more convenient thing be made available under the more convenient name, and to make the more powerful but less convenient thing available under the slightly less convenient name. (Note that the stackoverflow question you linked is about why one might prefer a Map over an object as a dictionary. But if you know the group names up front - as is often, but not always, the case with |
I think my remaining concern is this: what data is there that says that an object is a more common use case? due to the fact that Lodash, a very widely adopted library, uses object doesn't mean if it were to use Map instead that it wouldn't still be widely adopted I personally find myself using from what I can tell, by making again, my 2 cents here among many others; I appreciate the (re)consideration but I suppose if the decision has already been made and there is no data we can look at to say that one way is conclusively "better" than the other than we're left to move forward with what the authors/champions think is best; either way I'm excited to see |
I didn't claim use cases where an object suffices would be more common, just that they would be common. Which I think is plainly true and does not require data. |
I may have misunderstood this then:
I think you make a good point that it will be common to use As such, both use cases may be common and I don't think we have any data that says one will be more common. So in my mind it seems that as a i.e. I propose that given the lack of data for which use cases will be most common that whichever is most versatile be made the go-to (shorter name) and if that is the criteria then I believe |
As an aside: I really appreciate all the conversation here. I do not think it is an easy call as to whether to have one or two I appreciate the willingness to discuss, share, and discover (perhaps even rediscover in some cases) the advantages and disadvantages. Regardless of one or two methods and their names I am excited to see this come about as I think it will be very useful! |
My point is that working with a Map is usually less convenient than working with an object: you have to call We have to make one of the two cases slightly more awkward to use, by giving it the non-default name. My preference is to make the Map case, which is already inherently somewhat awkward, be slightly more so: this is a small cost, because the relative increase in awkwardness is low. By contrast, if we make the object case, which is not already inherently awkward, be slightly more awkward, that is a large cost, because the relative increase in awkwardness is larger (even though the total increase is the same). This is what I meant above by "I think it makes sense to have the more convenient thing be made available under the more convenient name, and to make the more powerful but less convenient thing available under the slightly less convenient name." To put it another way: there are never going to be very short idioms possible in the Map case because of the inherent inconvenience of using Maps, so making these use cases require a wordier method name doesn't cost us much. On the other hand, there are short idioms possible in the object case, as in |
My point is that the decision on which use cases to make more convenient should be based on empirical data. To my knowledge we don't collectively have empirical data that says that destructuring and/or JSON stringification is a more common use case than iterating over the groups. I often find myself doing both, it simply depends on the use case and I often want to destructure and I often want to iterate on or transform somehow the group keys and/or values before further use. As such, what I am proposing is that the more versatile / general purpose / adaptable return type be given the shorter name if one is to have a shorter name. On that note I suppose a different idea is to not prefer one over the other at all but to name the methods I think there should be a good reason to give one the shorter name over the other and from what I can tell we can't know which will be used more often and as such I am suggesting that we shouldn't make one more preferred than the other unless there is empirical data to say that it should be so. |
Perhaps some data could be found by analyzing lodash groupBy usage? Even though it's always objects there AFAIK, comparing the frequency of use with fixed set of keys with the frequency of use with arbitrary/calculated keys seems like a passable proxy for whether object (in the first case) or map (in the second) is more likely to be the natural choice. Unfortunately, determining what's fixed vs arbitary doesn't seem friendly to automation, so it may only be realistic with a sample small enough for by-hand classification. |
If you can figure out how many times Github Search is quite inadequate for this task, so if someone wanted to present such data, that'd be interesting to look at. |
Empirical data can't tell us about all possible future usage; it's really not all that helpful. For example, most kitchen-sink libraries which include Without knowing the future, we have to rely on our judgement and on other reasoning. And in my judgement it makes sense for the key-value collection which has explicit support in the syntax of the language to be the preferred return type here. I think this is a very good reason to prefer one over the other. |
Yeah, I didn't know whether there was a realistic way to collect that data and unfortunately there may not be. It was a "here's a thing one could theoretically measure" suggestion for @mfulton26 because "empirical data" about things like "frequency of serializing result to JSON" seemingly couldn't say anything: it doesn't compare to anything else. To have any chance of being useful input, data would need to groupBy(whetherObjectOrMapIsLikeyBetterSuited)). @bakkot I think that if someone is able to collect data about something like this, that's sort of interesting in its own right and potentially useful whether or not it turns out to have a bearing on this specific decision, so would not want to discourage it. FWIW, I agree with your conclusions ("this is almost always for destructuring" matches my experience). |
Yeah, I don't mean to discourage doing this kind of research, just relying on it. Re: GitHub Search - SourceGraph works ok for this sort of query, in my experience. |
I doubt much research would really help as neat as it might be to do. Without Lodash having unbiasly named groupBy functions I think the results works overwhelmingly be in favor of object and not Map. |
I appreciate the discussion here and it seems that my concerns and opinions are not shared and that is okay. Is there anyone else's comments we should wait for before closing this out? @jridgewell perhaps? |
I strongly prefer the object version, and imagine objects (and destructuring) will be the expected output for regular users. As cool as Maps are, the JS ecosystem prefers objects for just about everything. |
Thank you everyone. One last thought that I didn't think of before (and perhaps it has already been discussed somewhere too) but instead of a null-prototype would it be worth exploring adding a prototype that doesn't extend Object's prototype and has function named array.groupBy(…).asMap(); this wouldn't disrupt destructing or JSON serialization and I think it more ergonomic than a separate method thoughts? |
If it was an object with toObject and toMap, you’ve made the convenient case less so. If it’s an object with toMap on it, you’ve broken anyone who wants a key of “toMap”. |
In addition to the issue ljharb described, that approach would mean Map keys need to be cast to property keys as well. That’s a problem not just because of “extra work” but because |
I suggest renaming
groupBy
andgroupByToMap
togroupByToObject
andgroupBy
respectively or dropping support for an object altogether.there are various reasons to prefer
Map
overObject
reasons from MDN
Map
does not contain any keys by default. It only contains what is explicitly put into it.An
Object
has a prototype, so it contains default keys that could collide with your own keys if you're not careful.Note: As of ES5, this can be bypassed by using {{jsxref("Object.create", "Object.create(null)")}}, but this is seldom done.
Map
's keys can be any value (including functions, objects, or any primitive).Object
must be either a {{jsxref("String")}} or a {{jsxref("Symbol")}}.The keys in
Map
are ordered in a simple, straightforward way: AMap
object iterates entries, keys, and values in the order of entry insertion.Although the keys of an ordinary
Object
are ordered now, this was not always the case, and the order is complex. As a result, it's best not to rely on property order.The order was first defined for own properties only in ECMAScript 2015; ECMAScript 2020 defines order for inherited properties as well. See the OrdinaryOwnPropertyKeys and EnumerateObjectProperties abstract specification operations. But note that no single mechanism iterates all of an object's properties; the various mechanisms each include different subsets of properties. ({{jsxref("Statements/for...in", "for-in")}} includes only enumerable string-keyed properties; {{jsxref("Object.keys")}} includes only own, enumerable, string-keyed properties; {{jsxref("Object.getOwnPropertyNames")}} includes own, string-keyed properties even if non-enumerable; {{jsxref("Object.getOwnPropertySymbols")}} does the same for just
Symbol
-keyed properties, etc.)Size
Map
is easily retrieved from its {{jsxref("Map.prototype.size", "size")}} property.Object
must be determined manually.Map
is an iterable, so it can be directly iterated.Object
does not implement an iteration protocol, and so objects are not directly iterable using the JavaScript for...of statement (by default).Note:
Object.keys
orObject.entries
.Performs better in scenarios involving frequent additions and removals of key-value pairs.
Not optimized for frequent additions and removals of key-value pairs.
destructuring and transformation for JSON stringification can be accomplished from a
Map
easily enough usingObject.fromEntries
:groupBy
(to a null-prototype object) may set a precedence for future features to avoid defaulting to more proper keyed collections likeMap
andSet
and use objects and arrays instead; these have been used historically and continue to be used prevalently (from what I can tell) even when there are now these more optimized/appropriate collections availablein short, making it easy for developers to use a
Map
seems like a win to me and paves the way for more rich data structures and utilities using them and for those use cases where anObject
is wanted (destructuring and JSON stringification) then there is a built-in way to quickly, efficiently, and (IMO) ergonomically transform the returnedMap
to anObject
. If further convenience/ergonomics is desired then agroupByToObject
method could still be provided while the shorter go-to methodgroupBy
would return aMap
as the "preferred" type to work withThe text was updated successfully, but these errors were encountered: