Skip to content

Instantly share code, notes, and snippets.

@acutmore
Last active December 17, 2021 21:06
Show Gist options
  • Save acutmore/e7e70306dbc02e2e5fa6e340b09eaa15 to your computer and use it in GitHub Desktop.
Save acutmore/e7e70306dbc02e2e5fa6e340b09eaa15 to your computer and use it in GitHub Desktop.
Proof of concept: Userland Tuples as WeakMap keys - Ignores Records and Box(Box(obj)) for simplicity but theory should expand to work with these
// Assumes R&T uses Box instead of symbols-as-weakmap keys. But approach works for both.
// newer gist here: https://gist.github.com/acutmore/2aebf65223783bb452c37e4c3f1ac2a6
function tupleWalk(t, cb) {
for (const v of t) {
if (Tuple.isTuple(v)) {
tupleWalk(v, cb);
}
else {
cb(v);
}
}
}
class TupleWeakMap {
/** Standard WeakMap for storing non Tuple values */
#weakmap = new WeakMap();
/**
* Standard Map for storing Tuple values.
* Tuples have any Object references removed before being stored here
*/
#tupleMap = new Map();
/** A lookup for unique symbols to represent Objects */
static #objectSymbolWeakMap = new WeakMap();
/**
* Walk a Tuple replacing Boxes with Symbols, so we don't have any strong references to objects
*/
static #removeObjectReferences(v) {
if (Box.isBox(v)) {
const boxedValue = v.unbox();
let sym = TupleWeakMap.#objectSymbolWeakMap.get(boxedValue);
if (sym === undefined) {
sym = Symbol();
TupleWeakMap.#objectSymbolWeakMap.set(boxedValue, sym);
}
return sym;
}
if (Tuple.isTuple(v)) {
return v.map(x => TupleWeakMap.#removeObjectReferences(x));
}
return v;
}
/** Does the tuple contain a box that contains an object */
static #tupleWithIdentity(v) {
if (!Tuple.isTuple(v)) return false;
let valid = false;
tupleWalk(v, v => {
if (Box.isBox(v)) {
const boxedValue = v.unbox();
if (Object(v) === v) {
valid = true;
}
}
});
return valid;
}
delete(key) {
if (TupleWeakMap.#tupleWithIdentity(key)) {
return this.#tupleMap.delete(TupleWeakMap.#removeObjectReferences(key));
} else {
return this.#weakmap.delete(key);
}
}
get(key) {
if (TupleWeakMap.#tupleWithIdentity(key)) {
return this.#tupleMap.get(TupleWeakMap.#removeObjectReferences(key));
} else {
return this.#weakmap.get(key);
}
}
has(key) {
if (TupleWeakMap.#tupleWithIdentity(key)) {
return this.#tupleMap.has(TupleWeakMap.#removeObjectReferences(key));
} else {
return this.#weakmap.has(key);
}
}
set(key, value) {
if (TupleWeakMap.#tupleWithIdentity(key)) {
// safeKey is a replica of the Tuple but with Symbols representing the Boxes
// this means that there won't be strong references from the Map to the Objects
// so they can be collected as usual.
let safeKey = TupleWeakMap.#removeObjectReferences(key);
if (!this.#tupleMap.has(safeKey)) {
const mapRef = new WeakRef(this.#tupleMap);
// TODO move fr out so it is not collected too soon
const fr = new FinalizationRegistry(() => {
if (safeKey !== undefined) {
mapRef.deref()?.delete(safeKey);
}
// finilize no more
fr.unregister(fr);
safeKey = undefined;
});
fr.register(this.#tupleMap, null, fr);
tupleWalk(key, v => {
if (Box.isBox(v)) {
const boxedValue = v.unbox();
if (Object(boxedValue) === boxedValue) {
fr.register(boxedValue, undefined, fr);
}
}
});
}
return this.#tupleMap.set(safeKey, value);
} else {
return this.#weakmap.set(key, value);
}
}
}
@acutmore
Copy link
Author

acutmore commented Jul 20, 2021

tuple-in-weakmap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment