-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} | |
} |
Author
acutmore
commented
Jul 20, 2021
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment