Skip to content

Instantly share code, notes, and snippets.

@acutmore
Last active June 16, 2022 09:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acutmore/2aebf65223783bb452c37e4c3f1ac2a6 to your computer and use it in GitHub Desktop.
Save acutmore/2aebf65223783bb452c37e4c3f1ac2a6 to your computer and use it in GitHub Desktop.
DEPRECATED (FLAWED) - Proof of concept: Tuples (with symbols) as WeakMap keys in Userland
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// this approach fails to handle cycles
// next attempt: https://gist.github.com/acutmore/d1aaaff27c898fdd26b2e15de5c2f7c6
const {Tuple, FakeSymbol} = ToyTupleLib();
const Box = BoxLib(FakeSymbol);
const TupleWeakMap = TupleWeakMapLib(Tuple, FakeSymbol, v => FakeSymbol.isFakeSymbol(v));
const wm = new TupleWeakMap();
let t = Tuple(1, 2, Tuple(3, Box( new Set() )));
wm.set(t, 42);
console.assert(wm.has(t));
console.assert(wm.get(t) === 42);
t = null;
// triggering gc should now log:
// > collected tuple [1, 2, {"__tuple__": 1}]
// > collected tuple [3, {"__symbol__": 0}]
///////////////////////////////////////////////////////////////////////////////
// Libs:
function TupleWeakMapLib(Tuple, symbolConstructor = Symbol, isSymbol = v => typeof v === 'symbol') {
function tupleWalk(t, cb) {
if (Tuple.isTuple(t)) {
for (const e of t) {
tupleWalk(e, cb);
}
} else {
cb(t);
}
}
const symbolToReplacement = new WeakMap();
function replaceSymbol(sym) {
if (symbolToReplacement.has(sym)) {
return symbolToReplacement.get(sym);
}
const replacement = symbolConstructor();
symbolToReplacement.set(sym, replacement);
return replacement;
}
return class TupleWeakMap {
#fr = new FinalizationRegistry(key => {
console.log(`TupleWeakMap dropped`, { key, value: this.#map.get(key) });
this.#map.delete(key);
});
#map = new Map();
#tupleContainingSymbol(t) {
let foundSymbol = false;
tupleWalk(t, v => {
foundSymbol ||= isSymbol(v);
});
return Tuple.isTuple(t) && foundSymbol;
}
#getTupleShape(t) {
if (Tuple.isTuple(t)) {
return Tuple(...t.map(v => this.#getTupleShape(v)));
} else if (isSymbol(t)) {
return replaceSymbol(t);
} else {
return t;
}
}
set(tuple, v) {
if (!this.#tupleContainingSymbol(tuple)) {
throw new Error('invalid TupleWeakMap key');
}
const safeKey = this.#getTupleShape(tuple);
tupleWalk(tuple, v => {
if (isSymbol(v)) {
this.#fr.register(tuple, safeKey);
}
});
this.#map.set(safeKey, v);
return this;
}
has(k) {
if (!Tuple.isTuple(k)) {
return false;
}
const safeKey = this.#getTupleShape(k);
return this.#map.has(safeKey);
}
get(k) {
if (!Tuple.isTuple(k)) {
return undefined;
}
const safeKey = this.#getTupleShape(k);
return this.#map.get(safeKey);
}
delete(k) {
if (!Tuple.isTuple(k)) {
return false;
}
const safeKey = this.#getTupleShape(k);
return this.#map.delete(safeKey);
}
}
}
function BoxLib(symbolConstructor = Symbol) {
const objToSym = new WeakMap();
const symToObj = new WeakMap();
function Box(v) {
if (objToSym.has(v)) {
return objToSym.get(v);
}
const sym = symbolConstructor();
symToObj.set(sym, v);
objToSym.set(v, sym);
return sym;
}
Box.unbox = function(v) {
return symToObj.get(v);
};
return Box;
}
function ToyTupleLib() {
let nextId = 0;
const store = new Map();
const tupleIds = new WeakMap();
const symbolIds = new WeakMap(); // fake symbols to represent 'symbols as weakmap keys'
const fr = new FinalizationRegistry(h => {
console.log(`collected tuple ${h}`);
const wr = store.get(h);
if (wr.deref()) {
store.delete(h);
}
});
function hash(t) {
return JSON.stringify(t, (_, v) => {
switch (typeof v) {
case 'undefined':
case 'number':
case 'string':
case 'boolean':
return v;
case 'object':
if (v === t) {
return v;
}
if (v === null) {
return { __null__: true };
}
if (tupleIds.has(v)) {
return { __tuple__: tupleIds.get(v) };
}
if (symbolIds.has(v)) {
return { __symbol__: symbolIds.get(v) };
}
}
throw new Error('invalid toy tuple content');
});
}
function Tuple(...values) {
const h = hash(values);
for (const [k, v] of store) {
if (!v.deref()) {
store.delete(k);
}
}
if (store.has(h)) {
const wr = store.get(h);
if (wr.deref()) {
return wr.deref();
}
}
Object.freeze(values);
store.set(h, new WeakRef(values));
fr.register(values, h);
const id = nextId++;
tupleIds.set(values, id);
return values;
}
Tuple.isTuple = function(t) {
return tupleIds.has(t);
};
// Stand-in for symbols-as-weakmap-keys
function FakeSymbol(desc) {
const id = nextId++;
const s = Object.create(null);
s.toString = () => `[symbol ${id}]`;
Object.freeze(s);
symbolIds.set(s, id);
return s;
}
FakeSymbol.isFakeSymbol = function(s) {
return symbolIds.has(s);
}
return { Tuple, FakeSymbol };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment