The idea is that TwoWayWeakMap would work like WeakMap, except that you would be able to get key objects by their value. For example, here is how the usage would look like:
class TwoWayWeakMap {
// What goes here?
}
class SomeClass {}
const map = new TwoWayWeakMap<SomeClass, number>()
function main() {
const o = new SomeClass
map.set(o, 42)
console.log(map.get(o)) // logs "42"
console.log(map.keyFrom(42)) // logs "SomeClass {}" (the `o` object)
}
main()
Once main() completes, I would expect the o key to be collected at some point when it is no longer referenced.
Calling keyFrom with a value that was mapped from the object would return undefined, which would be similar to WeakRef returning undefined after an object has been collected.
I gave it a shot using WeakRef, but it doesn't seem that the object is ever collected. Maybe I missed something silly. If the following example were to work as I intended, then eventually it would log "o was collected!", but you'll see in your console that the object persists, even after forcing garbage collection in Chrome's devtools:
console.log keeps the object alive. Strangely enough, even if you only print it once, then clear the console. Adjust these two lines so that console.log doesn't get hold of the object:
console.log(`o still exists? (${tick++})`, !!obj);
...
console.log(!!map.keyFrom(42));
for (const ref of this.#refs) {
const o = ref.deref();
if (!o) {
this.#refs.delete(ref);
return undefined; // <<< I don't think returning here is correct, the ref could have been for a different key/value pair
}
if (this.get(o) === v) return o;
}
Maybe this:
for (const ref of this.#refs) {
const o = ref.deref();
if (o === undefined) {
this.#refs.delete(ref);
} else {
if (this.get(o) === v) return o;
}
}
Hey, thanks! I had since then already changed that return undefined to continue. That's what I meant.
The example in the original post works now.
I wonder if it would be a bad practice to use this though. Like, for example, if some piece of code keeps accessing some object by providing the key that it maps to, then this could prevent the GC from collecting the object if it always happens before GC has a chance to run.
Would it be better if the language provided a LossyRef feature that would be like WeakRef, except that deref() would return undefined if the object is no longer reachable (even if not yet collected)?
Seems a feature like that would make code more robust and fail proof.
You probably just want to combine a WeakMap for object key to value and a WeakValueMap for value to object key which needs a finalization registry to clear things. You should never need to iterate over a set, or defer unless you actually mean to access that value. I'll try to write your TwoWayWeakMap a little later.
I switched your keyFrom to a keysFor since there can be multiple values with the same key. Also supports -0, doesn't prevent collection if there are cycles between keys and values, and most importantly, doesn't need to perform any whole map iteration. The only iteration is on keysFrom, and on a set of keys specific to the given value. It's also the only time any WeakRef is dereferenced, so it doesn't hold any objects alive unnecessarily.
Given how tricky this is to write correctly (you as a delegate making subtle mistakes implementing it is notable) and that there is some existing language precedent for implementing it at the language level (Lua's __mode = "kv" for tables - WeakMap would be __mode = "k", and __mode = "v" makes values weak for context), I feel it's definitely worth looking into as a new feature.
I understood the request as being able to find all the keys of a WeakMap that have a given value, but besides that for the map to still be a regular WeakMap where keys are held weakly, but the value strongly associated to the key. You are implementing a WeakKeyValueMap, where both the key and value are held weakly.
I thought I had a similar Weak key and value map implemented as a gist somewhere but I can't find it right now. It's roughly the same shape as yours, but I allowed the keys to be a primitive as well, so I used a Map instead of a WeakMap for them.
It should be noted that any new collection that holds the value weakly allows to observe the liveness of the value, which is the power granted by WeakRef/FinalizationRegistry and more powerful than WeakMap.
I believe we're on the same page. Sorry I originally mistyped and edited my post immediately. TBH, I don't quite remember the variety of specialized weak collections I've implemented over time, and mangled things at first.
I understood the original request to be for a regular WeakMap, with the addition of a key lookup by value, which is basically a reverse "weak map" from values to the set of corresponding keys. Since "values" can either be objects or primitives, it actuality has both a WeakMap for object values, and a WeakValueMap for primitives values.