Last active
June 16, 2022 09:27
-
-
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 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
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
// 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