TC39: Add Object.groupBy and Map.groupBy
github.com
github.com
(I guess one exception is Object.is, which takes two values as arguments. But even Object.is makes a lot of sense existing on the Object constructor because it exposes an important abstract operation (SameValue). Object.groupBy is only a utility function)
But a method named `groupBy` on iterators traditionally means a different thing: https://github.com/tc39/proposal-array-grouping/issues/51#is...
Global iterable type it's too late for, since there's many extant iterables in the language and on the web which don't have it in their prototype chain and can't reasonably be changed.
Browsers aren't going to break old pages.
I wonder is there is any proposal for a standard library with a well known URI.
A browser with ESM support should be able to provide its internal version or fallback with a polyfill using import maps. Something like: import {groupBy} from “std:arrays”
That can be also useful to standardize libs between Node,Deno,Bun without polluting the global namespace… and it’s backwards compatible
A better answer here might be to give the language a version declaration feature so browsers could reliably provide a backward-compatible interpretation of old scripts if necessary. If breaking changes are only made when there is great value in the change and no reasonable alternative then they shouldn't happen often enough for this kind of versioning to become an unmanageable burden.
Regarding versioning, see the immediately preceding question in the FAQ.
The arguments about completely removing the plugins were always questionable. Yes they had security problems. Now the attack surface they represented has largely been moved to the browsers themselves, which also have security problems, often of a similar nature and for similar reasons. Plugins had stopped being a vehicle for drive-by downloads and the like thanks to better browser safeguards like click-to-play long before they were decisively removed.
Some of the most popular programming languages in the world behave significantly differently between major versions and have developed effective solutions to manage those differences. Sometimes those are as simple as a command line compiler flag or config file setting to specify a target language version. And of course the libraries used with many languages also frequently have to deal with complex dependency resolutions these days. I do not understand why JavaScript is special and could not adopt a similar model if the will was there.
As to versioning the language: languages like Rust and Go use editions, which allow them to making breaking changes to syntax but not to the standard library, which is what's being discussed here. Indeed Rust has several deprecated-but-unremovable things in their standard library. Python makes breaking changes to their standard library, and then people's code breaks. C++ requires you to specify the version for the entire program, which isn't viable for JS because pages mix scripts from dozens of authors, which all need to interact and to have a coherent view of the world. Not sure what other language you're thinking of.
The relatively unique object model in JS, and the fact that the standard library consists of ambient properties, also makes it special; it's harder in JS than it would be in a language like Rust to detect use of a particular feature of the standard library.
there is definitely an approach out there that breaks fewer pages than things like the flash EOL broke
And in a lot of cases it's not obvious that your code is going to be incompatible. For example the most recent case there was code which was like `function foo(x) { if (x.group) x = x.group; /.../}` where that function was being called with `foo({ group: [...] })` or `foo([...])`. That code breaks if `Array.prototype.group` is added. No one is going to figure that out themselves unless they test on a version of a browser which ships `Array.prototype.group`, and empirically no one does that.
Yes, we could find a way to break fewer pages than flash EOL broke. But even just a handful of pages people actually use is too much. No browser wants to ship an update which will break (to pick a relevant example) the website of the government of Brazil, even if they somehow "ought to have" updated their code before that update. Developers are empirically not going to do that update and browsers are not going to punish the users of the pages of those developers by breaking them.
The return value of groupBy is an object that does not inherit from %Object.prototype%.They're "usually used as a cheap substitute for maps": the lack of prototype prevents the object from being polluted by keys that morally shouldn't be there, like toString, etc.
See https://github.com/tc39/proposal-array-grouping for why this isn’t a method on Array.prototype.
If we added like four new kinds of object/map, that complicates things… but just formalizing more common access and iteration patterns for existing structures seems low risk.
What I can’t wait for are all the set operations that would make ‘Set’ truly powerful.
That seems like a fine goal. Allow runtimes to execute JS with type annotations as-is.
1. Adds complexity to the language
2. Adds complexity to engines
3. Adds complexity to developers, especially new developers ("wait is it typed or not")
4. Most importantly, all but guaranties we will never have true types in JavaScript for things that could benefit from it like node or electron where instant compile time isn't necessary.
All this for a feature only helping some developers some of the time so they can run code that will be stripped out in production to reduce size anyway on their local browser a bit more easily.
I no longer install lodash that often anymore
This flexible primitive enables you to do basically any grouping transform you want (incl. the Lodash-style keyBy) in a single short line of code.
It's a lot like a sortBy operation, in that to emulate it, you would have to do a map to extract the key, producing pairs; sort (or in this case group) the pairs; and then deep-transform the pairs inside the data structure by unwrapping the keys off them. In other words, it's something that's a bit too high-friction to reach for if the language doesn't just give it to you (you'd probably do what you're planning to do some other way); but if the language does give it to you, you'll use it quite often.
Array.prototype.map
in the parameter for Object.fromEntries
in an easy way (probably not as optimized as a built-in, but that might be irrelevant for most cases, since its just a duplicated iteration, not quadratic)More broadly, this is an example of a utility function for organizing data in a data structure, which JavaScript and all other major programming languages are well-equipped to implement in arbitrary ways, so this is just a convenience function. It's not like a hook into underlying environment APIs that can't be independently shimmed.
This is probably missing checking for a bunch of corner cases, but this is the general idea:
// quick & dirty Object.groupBy
function groupBy(iterable, callbackFn) {
const out = {}
for(const [key, value] in Object.entries(iterable)) {
const name = callbackFn(value, key)
(out[name] || (out[name] = [])).push(value)
}
return out
}
I literally wrote something very like this yesterday (after checking & finding it only got added to Node 21, and deciding not to pull in a dependency).Edit: had keyBy impl in here, fixed! Thanks commenters!
I remember me running into the problem of internal dependencies when extracting parts of it into a non-ESM/non-build project some years ago...
Like, the internal consistency of the library probably benefits, and with tree-shaking it works reasonably well but it is still a poster example of DRY vs KISS...
It has its benefits, but its API surface can be annoying, too. When I encounter it, I often have to think for a second and look at the docs, e.g. some cryptic xor or map function with three parameters, one of which being an optional config object.
OTOH, it can be great to have a well-tested implementation at hand for all kinds of higher-order functions, like throttle, debounce etc
Object.from(null), always.
https://gist.github.com/ricardobeat/040af80971273f5abd71c2bf...
I got tired, but wouldn't be surprised if the whole thing goes beyond 1000 lines. It's basically a black hole. Better hope you really, really trust their test suite, it's impossible to debug and there must only be a handful of people familiar with the whole codebase.
JS standard library is notoriously deficient.
This is a good addition in my view too, especially including Map.
When dealing with DB/API results, one can of course always say that such grouping in the client is an anti-pattern.
But in reality it can be reasonable and useful.
If you write one-off algorithms in JS to transform small amounts of data on the client, you probably habe done this.
And even for data-heavy applications, it could prove to be a performance benefit for functions that use Map instances during computation.
Though that's just wishful thinking until the runtime developers choose to optimize these functions, if possible.
It depends on how many data you're working on. This groupBy is useful for filters in data tables.
If you have to download a zillion of records and filter them, client side filtering is a bad idea. If you have a few thousands of them, it could possibly be the faster solution overall.
Except oddities like C, C++, Perl, PHP, and Go :)
Array.product would be sweet as well (i.e. like product in python's itertools).
babel.config.js is just a mistery to me. @babel/present-env, targets: { node: 'current' }, forceAllTransforms, useBuiltIns, 'corejs', modules, @babel/plugin-proposal-object-rest-spread.
I mean, I generally dump it into Chat GPT and ask for an explainer, but... how can I know for sure I can use `array.reverse()` and it will be correctly handled in older browsers?
And how does the babel version in yarn.lock relate to all this?
What about the .browserslistrc which contains 'defaults' ?
My god.
Enable the "show obsolete platforms" checkmark to see the data for older versions of Babel/core-js. But probably you should just upgrade that instead.
I noticed this part in the proposal
groupBy calls callbackfn once for each element in items, in ascending order, and constructs a new Object of arrays. Each value returned by callbackfn is coerced to a property key
Does JS allow arbitrary objects as keys? I am asking because `group-by` in ClojureScript is quite flexible. e.g. you can find anagrams like so (def words ["meat" "mat" "team" "mate" "eat" "tea"])
(group-by set words) ; set is a function that creates sets from collections
;; => {#{\a \e \m \t} ["meat" "team" "mate"]
;; #{\a \m \t} ["mat"]
;; #{\a \e \t} ["eat" "tea"]}
I am wondering how one could translate this using Object.groupBy as specified in the proposal.The symbol primitive is also allowed as an object key, using square bracket access.
And arrays are "exotic objects" that have some special behaviors around their keys (auto-updating length property)
objects = {}
items1 = ["a", "b"]
items2 = ["a", "b"]
objects[items1] = 1
objects[items2] = 2
then objects[items1] will return 1, and objects[items2] will return 2, but objects[items1] !== objects[items2].EDIT: Sorry, this only works for dictionaries that are Maps, not Objects! See the responses. My fuzzing about in the console led me astray; you should start with `objects = new Map()` instead of `objects = {}`.
> o = { [{}]: 1 }
{ '[object Object]': 1 }
> k = { toString() { return 'a' } }
{ toString: [Function: toString] }
> o = { [k]: 1 }
{ a: 1 } new Proxy(
{},
{
get(target, property) {
return typeof property
},
},
)[{}] === 'string'With a map it works like you described:
objects = new Map()
items1 = ["a", "b"]
items2 = ["a", "b"]
objects.set(items1, 1)
objects.set(items2, 2)Thanks for taking the time to explain, everybody
I've wanted to learn Clojure for years but haven't found the right reason, until discovering Logseq. Such a cool language!
Object doesn't, but Map does. It's generally a good idea to use Map anyway for dynamic keys.