Maybe im being naive, but i want more iter methods on nearly all collection primitives. {}.map? someSet.reduce? Ya, sure, some default behavior would need to be assumed. I wager most practitioners would delight in it.
While we’re here, find_map, filter_map, take, take_while… i want excellent std iter tools. They’re not big or complicated impls, but they sure would have big impact if available out of the box!
(They're not on objects, which I think is probably for the best; every method on Object.prototype is a shadowing risk and so we're unlikely to see any besides the ones that already exist. You can always use Object.entries; if your object has so many dynamically-computed properties that eagerly collecting them is a performance concern, then it should probably be a Map.)
Not saying anyone SHOULD do this... just that one _could_.
derefr 148 days ago [-]
> if your object has so many dynamically-computed properties that eagerly collecting them is a performance concern, then it should probably be a Map
Should be, but there's so much legacy code that is going to hand you objects.
Also, unless you go to a weirdly large amount of effort, JSON.parse will give you objects.
tracker1 147 days ago [-]
Even then... [Object] could just iterate [k,v] unless the behavior is overridden... even then that's probably appropriate for something like weakmap anyhow.
cdaringe 147 days ago [-]
Thats a nice set of improvements! Thx for the ref
On the proto extension: Sure. Fair argument. Id still be interested in a world where theyre there and non enumerable, or there but behind an .iter() field.
nsonha 148 days ago [-]
Maybe Deno @std/collections helps, it's now a separare library:
I don't understand the use case, can someone enlighten me? I've always used them as a caching mechanism for derived / expensive data, and they work perfectly for that as-is. If you want to enumerate them I can't help but think your mental model is wrong and you actually don't want the keys to ever be released from memory.
davexunit 148 days ago [-]
I'll try my best to explain.
The use case for Andy here is building backtraces for Scheme compiled to WebAssembly. To print a meaningful backtrace, a Wasm funcref for a compiled Scheme function needs to be mapped to a code origin (file name, line, column). Unfortunately, Wasm can't compare funcrefs, so the host needs to do it. Andy's solution is to have each Scheme Wasm module maintain a WeakMap of funcrefs to code origins. However, this alone is not enough because there may be many Scheme modules that are sharing resources and any given funcref could come from any of the modules. So, the runtime needs to maintain a weak set of all modules so that you can query for the code origin for any funcref and the runtime can search all the modules to find it. A WeakSet would be enough for this task... if it were iterable. To work around this, Andy made an IterableWeakSet which has the drawbacks noted in the blog post.
I also ran into another use case recently while trying to port a library written in a language with iterable weak maps to Wasm with JS providing the weak map support. I'll have to do a very similar, unsatisfying workaround there, too.
ralusek 148 days ago [-]
I use WeakMaps all the time. While most cases where I’ve wanted to iterate through a WeakMap are achievable through other means, it usually comes at an inconvenience that usually greatly reduces the utility of using the WeakMap in the first place.
If you’re using a WeakMap to avoid having to do lifecycle-type logic of removing from a map(s) under such and such circumstances, it would often be nice to be able to eschew such lifecycle logic by having items be automatically garbage collected, while still being able to iterate through the items in the map.
derefr 148 days ago [-]
How about iterating them to find and flush cache entries with expired TTLs, before they're weak-expired?
catapart 147 days ago [-]
This might not be a good use case because I just started doing it and never found a good reason to stop, but I use WeakMaps for keeping references to HTML elements at runtime in library code where I don't want to require a constructor/deconstructor pattern.
Basically, I use them as a reference for DOM elements that my library will do work on, since I can't predict when that DOM element will no longer be a viable reference. And for that - yeah, I can certainly see some benefits to making the thing iterable. Not that I've hit any roadblocks replacing the WeakMap with an array didn't fix. Just that my library array will keep the ref alive until the dev dismisses it. Being able to iterate on WeakMaps would at least give that memory back.
jauntywundrkind 149 days ago [-]
Heck yes. The arguments against iterability felt so weak to me; going far back but it felt like concerns about a lack of determinism & ability to well specify what guarantees could be offered.
My heavens thought, not offering this has been such a vast impediment. Ideally we would have a registry of annotation/observability data ready for us in WeakMap, but these design limitations/fears have previously wounded the effort.
The point that we have WeakRef now & can workaround this, but it's ugly & slow, heightens the absurdity of the current situation.
olliej 147 days ago [-]
The core problem with WeakMap in JS (from a regular developer point of view) is that it was originally intended to be what PLT folk call an Ephemeron Table, rather than what “weak map” means to everyone else :D
The principle use case for them - from the PoV of MarkM and Crockford (IIRC) was essentially private fields, which is a thing we solved much better with the addition of `Symbol`/private names.
That’s why you have to have an object key in weakmaps, and as a result most of the uses for weakmap that I have encountered have been incorrect in that they inherently end up keeping the key object alive and that causes the associated value to stay alive (again JS WeakMap does not match the definition of WeakMap in any other language :-/)
A bunch of folk have made statements to the effect of “depending on behavior of iteration when iterating a weak map is bad code and the developers are wrong and preventing iteration to protect against bad code is dumb”. That’s not true for the web, for myriad reasons, but the fix of it is that if a major site ends up being meaningfully dependent on GC behaviour in Chrome then the site support will just say “you need to use chrome” - and then every other browser will need to work out how to get close enough semantics. An easy way to understand how this can meaningfully impact behaviour is to consider generational collectors vs non-generational: a generational collector can very easily end up keeping a cache entry alive longer and then iteration allows it to come back to life. The user experience here is Browser 1 is worse than browser 2 because it has to keep downloading data as the cache gets evicted.
A more period relevant reason for the “there shall be no iteration” is that at the time there was no standardized definition of when references were permitted to (observably) die.
That is less of an issue today: when the language finally added a real weak reference type that finally had to be addressed, and now the JS spec specifies run loop semantics governing when weak references can die so that you get a sufficiently reasonable amount of determinism in behaviour for them to be exposed to web content, so to an extent the problems of WeakMap iteration are less of a problem.
147 days ago [-]
148 days ago [-]
sfink 147 days ago [-]
No, they shouldn't. Iterability was not avoided to enable the inverted representation (where an object that is a key in a WeakMap stores a hidden WeakMap entry inside it). And I suspect even with an inverted representation, there's a decent chance there would still be an internal collection of keys for a given WeakMap in order to optimize tracing.
WeakMaps aren't iterable because that would make them a different data structure with different purposes and footguns and forward compatibility. The existing WeakMap is very nice in that there is no observable behavior change if GC starts triggering at a different time. You can't write an application that breaks in a new browser version because the internal GC scheduling was updated, or if your allocation patterns change from when you tested, or whatever. If you have the weakmap and the key, you can get the value. If you don't have one or the other, you can't, and the fact that the GC removes the entry behind your back makes no difference to anything but memory usage.
If you can iterate, then suddenly you can start racing the GC. Let's say your values are shared with some other data structure that uses them, and you have a lastUpdated timestamp on them that you update by iterating over the WeakMap. If you lose a key, you may or may not update lastUpdated on its associated value, depending on when the GC runs. (In this scenario, the value's lifetime is not dependent on the WeakMap; it's always reachable through some other path so you can observe lastUpdated.)
These sorts of footguns now exist via WeakRef and FinalizationGroup, but the entire purpose of those two is to be GC-sensitive. The existing, safe, forwards-compatible WeakMap has many valid uses. Adding iterability would defeat some but not all of those uses. Which means that rather than changing WeakMap, you'd want both WeakMap and IterableWeakMap.
(It would be nice if WeakMap had a different name. It's not "weak" in the sense that WeakRef is weak. A WeakRef says "I won't keep my target alive, but if it's alive for some other reason or GC just hasn't run yet, I'll give it back to you when you ask." A WeakMap says "if a weakmap and one of its keys is alive, I'll keep the value associated with that key alive." It says nothing about whether GC has run. It doesn't care whether something in the rest of the object graph is keeping the key alive.)
Whether engines should support a built-in IterableWeakMap is arguable. It's hard to implement correctly in JS using WeakMap + WeakRef, which argues for its inclusion. But it's also a lot of internal complexity in the engine for something so footgun-shaped. Yet it's really useful when it's what you need. But if it existed, people would be tempted to use it, and probably many or most of those uses would be incorrect (why use the nerfed WeakMap when IterableWeakMap exists? Just don't iterate it, if it bothers you!) But it's not the JS engine's job to prevent coders from writing bad code. But tests could fail intermittently without being buggy. Et cetera.
steelbrain 148 days ago [-]
Yeah, no. Bad idea.
I could very easily see an evolution speed run here where people do for…of on the weakmap, make closures that use those values and the closure references cause nothing in the WeakMap to do its job, which is get garbage collected.
Then we’ll see a submission on HN that says no, weakmaps should be weak…maps. Followed by intense wars on X/Twitter and then the next version of whatever linter is popular at the time adding linting rules that forbid people from iterating on weak maps.
zarzavat 148 days ago [-]
You can already build iterable WeakMaps. And people do, because they need iterable WeakMaps.
Your argument applies just as much to iterable WeakMaps distributed as libraries as it does to iterable WeakMaps distributed with the browser.
bjoli 148 days ago [-]
That sums up the arguments as:
1. Iterable weakmaps would be very usable to - among other things - foreign runtimes in JavaScript
2. People who don't know what they are doing can do dumb things with weak references.
Both are of course valid, but in this case I do think that linters telling people that they have to know what they are doing are the better option.
jongjong 147 days ago [-]
Just don't use weakmaps. Clean up your maps instead.
steve_adams_86 147 days ago [-]
Weak maps offer better performance in some cases and reduce code complexity though. Cleaning up your maps approximates the same outcome but requires more logic to do it at no benefit to your application.
Weak maps also have the benefit of explicitly showing how the data is meant to be managed; they have a distinct purpose. Regular maps are far more versatile, but this versatility doesn’t benefit use cases a weak map is suited to.
While we’re here, find_map, filter_map, take, take_while… i want excellent std iter tools. They’re not big or complicated impls, but they sure would have big impact if available out of the box!
(They're not on objects, which I think is probably for the best; every method on Object.prototype is a shadowing risk and so we're unlikely to see any besides the ones that already exist. You can always use Object.entries; if your object has so many dynamically-computed properties that eagerly collecting them is a performance concern, then it should probably be a Map.)
Not saying anyone SHOULD do this... just that one _could_.
Should be, but there's so much legacy code that is going to hand you objects.
Also, unless you go to a weirdly large amount of effort, JSON.parse will give you objects.
On the proto extension: Sure. Fair argument. Id still be interested in a world where theyre there and non enumerable, or there but behind an .iter() field.
https://jsr.io/@std/collections/doc
The use case for Andy here is building backtraces for Scheme compiled to WebAssembly. To print a meaningful backtrace, a Wasm funcref for a compiled Scheme function needs to be mapped to a code origin (file name, line, column). Unfortunately, Wasm can't compare funcrefs, so the host needs to do it. Andy's solution is to have each Scheme Wasm module maintain a WeakMap of funcrefs to code origins. However, this alone is not enough because there may be many Scheme modules that are sharing resources and any given funcref could come from any of the modules. So, the runtime needs to maintain a weak set of all modules so that you can query for the code origin for any funcref and the runtime can search all the modules to find it. A WeakSet would be enough for this task... if it were iterable. To work around this, Andy made an IterableWeakSet which has the drawbacks noted in the blog post.
I also ran into another use case recently while trying to port a library written in a language with iterable weak maps to Wasm with JS providing the weak map support. I'll have to do a very similar, unsatisfying workaround there, too.
If you’re using a WeakMap to avoid having to do lifecycle-type logic of removing from a map(s) under such and such circumstances, it would often be nice to be able to eschew such lifecycle logic by having items be automatically garbage collected, while still being able to iterate through the items in the map.
Basically, I use them as a reference for DOM elements that my library will do work on, since I can't predict when that DOM element will no longer be a viable reference. And for that - yeah, I can certainly see some benefits to making the thing iterable. Not that I've hit any roadblocks replacing the WeakMap with an array didn't fix. Just that my library array will keep the ref alive until the dev dismisses it. Being able to iterate on WeakMaps would at least give that memory back.
My heavens thought, not offering this has been such a vast impediment. Ideally we would have a registry of annotation/observability data ready for us in WeakMap, but these design limitations/fears have previously wounded the effort.
The point that we have WeakRef now & can workaround this, but it's ugly & slow, heightens the absurdity of the current situation.
The principle use case for them - from the PoV of MarkM and Crockford (IIRC) was essentially private fields, which is a thing we solved much better with the addition of `Symbol`/private names.
That’s why you have to have an object key in weakmaps, and as a result most of the uses for weakmap that I have encountered have been incorrect in that they inherently end up keeping the key object alive and that causes the associated value to stay alive (again JS WeakMap does not match the definition of WeakMap in any other language :-/)
A bunch of folk have made statements to the effect of “depending on behavior of iteration when iterating a weak map is bad code and the developers are wrong and preventing iteration to protect against bad code is dumb”. That’s not true for the web, for myriad reasons, but the fix of it is that if a major site ends up being meaningfully dependent on GC behaviour in Chrome then the site support will just say “you need to use chrome” - and then every other browser will need to work out how to get close enough semantics. An easy way to understand how this can meaningfully impact behaviour is to consider generational collectors vs non-generational: a generational collector can very easily end up keeping a cache entry alive longer and then iteration allows it to come back to life. The user experience here is Browser 1 is worse than browser 2 because it has to keep downloading data as the cache gets evicted.
A more period relevant reason for the “there shall be no iteration” is that at the time there was no standardized definition of when references were permitted to (observably) die.
That is less of an issue today: when the language finally added a real weak reference type that finally had to be addressed, and now the JS spec specifies run loop semantics governing when weak references can die so that you get a sufficiently reasonable amount of determinism in behaviour for them to be exposed to web content, so to an extent the problems of WeakMap iteration are less of a problem.
WeakMaps aren't iterable because that would make them a different data structure with different purposes and footguns and forward compatibility. The existing WeakMap is very nice in that there is no observable behavior change if GC starts triggering at a different time. You can't write an application that breaks in a new browser version because the internal GC scheduling was updated, or if your allocation patterns change from when you tested, or whatever. If you have the weakmap and the key, you can get the value. If you don't have one or the other, you can't, and the fact that the GC removes the entry behind your back makes no difference to anything but memory usage.
If you can iterate, then suddenly you can start racing the GC. Let's say your values are shared with some other data structure that uses them, and you have a lastUpdated timestamp on them that you update by iterating over the WeakMap. If you lose a key, you may or may not update lastUpdated on its associated value, depending on when the GC runs. (In this scenario, the value's lifetime is not dependent on the WeakMap; it's always reachable through some other path so you can observe lastUpdated.)
These sorts of footguns now exist via WeakRef and FinalizationGroup, but the entire purpose of those two is to be GC-sensitive. The existing, safe, forwards-compatible WeakMap has many valid uses. Adding iterability would defeat some but not all of those uses. Which means that rather than changing WeakMap, you'd want both WeakMap and IterableWeakMap.
(It would be nice if WeakMap had a different name. It's not "weak" in the sense that WeakRef is weak. A WeakRef says "I won't keep my target alive, but if it's alive for some other reason or GC just hasn't run yet, I'll give it back to you when you ask." A WeakMap says "if a weakmap and one of its keys is alive, I'll keep the value associated with that key alive." It says nothing about whether GC has run. It doesn't care whether something in the rest of the object graph is keeping the key alive.)
Whether engines should support a built-in IterableWeakMap is arguable. It's hard to implement correctly in JS using WeakMap + WeakRef, which argues for its inclusion. But it's also a lot of internal complexity in the engine for something so footgun-shaped. Yet it's really useful when it's what you need. But if it existed, people would be tempted to use it, and probably many or most of those uses would be incorrect (why use the nerfed WeakMap when IterableWeakMap exists? Just don't iterate it, if it bothers you!) But it's not the JS engine's job to prevent coders from writing bad code. But tests could fail intermittently without being buggy. Et cetera.
I could very easily see an evolution speed run here where people do for…of on the weakmap, make closures that use those values and the closure references cause nothing in the WeakMap to do its job, which is get garbage collected.
Then we’ll see a submission on HN that says no, weakmaps should be weak…maps. Followed by intense wars on X/Twitter and then the next version of whatever linter is popular at the time adding linting rules that forbid people from iterating on weak maps.
Your argument applies just as much to iterable WeakMaps distributed as libraries as it does to iterable WeakMaps distributed with the browser.
1. Iterable weakmaps would be very usable to - among other things - foreign runtimes in JavaScript
2. People who don't know what they are doing can do dumb things with weak references.
Both are of course valid, but in this case I do think that linters telling people that they have to know what they are doing are the better option.
Weak maps also have the benefit of explicitly showing how the data is meant to be managed; they have a distinct purpose. Regular maps are far more versatile, but this versatility doesn’t benefit use cases a weak map is suited to.