Skip to content

Instantly share code, notes, and snippets.

@bmeck
Created August 9, 2019 05:13
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 bmeck/3955e617be395094432f4a3ceab23de7 to your computer and use it in GitHub Desktop.
Save bmeck/3955e617be395094432f4a3ceab23de7 to your computer and use it in GitHub Desktop.
'use strict';
/**
* @template {Object} O
* @constructor
* @param {O} o
* @returns {O}
*/
function Override(o) {
return o;
}
delete Override.constructor;
/**
* Used to create a private entity for storing data.
* Allows data to be stored directly on Objects and
* not using WeakMaps.
* @template {Object} O
* @template {any} V
*/
function Private() {
class PrivateDeclaration extends Override {
#data;
/**
* @param {O} o
* @param {V} v
*/
constructor(o, v) {
super(o);
// @ts-ignore
if (o !== this) {
throw new Error('Cannot install private data on a primitive');
}
o.#data = v;
}
setup() {
/**
* @param {O} o
* @param {V} v
* @returns {V}
*/
const insert = (o, v) => {
new PrivateDeclaration(o, v);
return v;
};
/**
* @param {O} o
* @param {() => V} insert
* @param {(v: V) => V} update
* @returns {V}
*/
const insertOrUpdate = (o, alloc = () => void 0, update = v => v) => {
let v;
try {
const old = get(o);
set(o, v = update(old));
} catch (e) {
insert(o, v = alloc());
}
return v;
}
/**
* @param {O} o
* @param {V} v
* @returns {V}
*/
const set = (o, v) => {
return o.#data = v;
};
/**
* @param {O} o
* @returns {V}
*/
const get = (o) => {
return o.#data;
}
/**
* @param {O} o
* @param {V} otherwise
* @returns {V}
*/
const getOr = (o, otherwise) => {
try {
return o.#data;
} catch {
}
return otherwise;
}
/**
* @param {O} o
* @returns {boolean}
*/
const has = (o) => {
try {
o.#data;
return true;
} catch {
}
return false;
};
return Object.freeze({
__proto__: null,
insert,
insertOrUpdate,
set,
get,
getOr,
has
});
}
};
delete PrivateDeclaration.constructor;
Reflect.setPrototypeOf(
PrivateDeclaration.prototype,
null
);
/**
* @type {typeof fake}
*/
const ret = PrivateDeclaration.prototype.setup();
return ret;
// the next line is wonky to make type inference happy
const fake = new PrivateDeclaration(/** @type {O} */({}), null).setup();
}
module.exports = Private;
@hax
Copy link

hax commented Dec 14, 2020

@bmeck I still not understand how this have difference with weakmap version in GC.

And assume the alternative syntax keep the same semantic, the try hack could be replaced by #data in o or o of class or class.checkBrand(o) without any difference.

@bmeck
Copy link
Author

bmeck commented Dec 14, 2020

@hax that assumes I wanted to compare all objects I attach data to against a class (which is multiple fields). The point of this is exactly that I am attaching data, not a class to arbitrary objects. It doesn't make sense to state that o of class Fake is true if it isn't a Fake

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