Skip to content

Instantly share code, notes, and snippets.

@michaelfig
Created February 12, 2021 01:52
Show Gist options
  • Save michaelfig/70acc9697e3dd04e6225d0aaef195835 to your computer and use it in GitHub Desktop.
Save michaelfig/70acc9697e3dd04e6225d0aaef195835 to your computer and use it in GitHub Desktop.
Half-fast membrane implementation
// @ts-check
/**
* @typedef {({} | Function) & 'Near'} NearRef Intersection to make compatible
* only with itself.
* @typedef {({} | Function) & 'Far'} FarRef Intersection to make compatible
* only with itself.
*/
/**
* @param {*} x
*/
const IDENTITY = x => x;
export const makeMembrane = (rootBlue, opts = {}) => {
const {
distortBlue = IDENTITY,
distortYellow = IDENTITY,
finishBlue = IDENTITY,
finishYellow = IDENTITY,
eager = false,
initialBlueYellowPairs = [
[Object.prototype, Object.prototype],
[Function.prototype, Function.prototype],
[Array.prototype, Array.prototype],
],
} = opts;
const initialYellowBluePairs = initialBlueYellowPairs.map(([b, y]) => [y, b]);
// TODO should verify that these are proper inverses
const blueToYellow = new WeakMap(initialBlueYellowPairs);
const yellowToBlue = new WeakMap(initialYellowBluePairs);
/**
* @param {WeakMap<NearRef, FarRef>} nearToFar
* @param {WeakMap<FarRef, NearRef>} farToNear
* @param {(xNear: any, makeNear: (xFar: FarRef) => NearRef) => any} distort
* @param {(xFar: FarRef, xNear: NearRef) => any} finish
* @param {'passBlueToYellow' | 'passYellowToBlue'} inverseName
*/
const makePass = (nearToFar, farToNear, distort, finish, inverseName) => {
/**
* @param {any} xNear
* @returns {any}
*/
function passNearToFar(xNear) {
// eslint-disable-next-line no-use-before-define
const passFarToNear = passFunctions[inverseName];
xNear = distort(xNear, passFarToNear);
if (Object(xNear) !== xNear) {
// Primitive value, no need to membranise.
return xNear;
}
// We now know we're dealing with an near object or function.
const xNearRef = /** @type {NearRef} */ (xNear);
/**
* @type {FarRef=}
*/
let xFar = nearToFar.get(xNearRef);
if (xFar) {
// Cached.
return xFar;
}
// We have an object or function.
if (typeof xNearRef === 'function') {
const fnNear = /** @type {Function} */ (xNearRef);
function fnFar(...argsFar) {
try {
const argsNear = argsFar.map(passFarToNear);
if (new.target) {
const newTargetNear = passFarToNear(new.target);
return passNearToFar(
Reflect.construct(fnNear, argsNear, newTargetNear),
);
}
const thisNear = passFarToNear(this);
return passNearToFar(Reflect.apply(fnNear, thisNear, argsNear));
} catch (eNear) {
throw passNearToFar(eNear);
}
}
/** Typecasts necessary */
const fnUnknown = /** @type {unknown} */ (fnFar);
xFar = /** @type {FarRef} */ (fnUnknown);
} else if (Array.isArray(xNearRef)) {
xFar = /** @type {FarRef} */ ([]);
} else {
xFar = /** @type {FarRef} */ ({});
}
nearToFar.set(xNearRef, xFar);
farToNear.set(xFar, xNearRef);
const xDescsNear = Object.getOwnPropertyDescriptors(xNearRef);
const xProtoNear = Object.getPrototypeOf(xNearRef);
const xProtoFar = passNearToFar(xProtoNear);
Object.setPrototypeOf(xFar, xProtoFar);
/**
* @param {[string, PropertyDescriptor]} param0
* @returns {[string, PropertyDescriptor]}
*/
const nearToFarMapper = ([name, vDescNear]) => {
if ('value' in vDescNear) {
let vDescFar;
if (eager) {
vDescFar = {
...vDescNear,
value: passNearToFar(vDescNear.value),
};
} else {
vDescFar = {
get: () => passNearToFar(vDescNear.value),
enumerable: vDescNear.enumerable,
configurable: vDescNear.configurable,
};
}
return [name, vDescFar];
}
const vFarDesc = {
...vDescNear,
get: passNearToFar(vDescNear.get),
set: passNearToFar(vDescNear.set),
};
return [name, vFarDesc];
};
Object.entries(xDescsNear)
.map(nearToFarMapper)
.forEach(([name, descFar]) => {
try {
Object.defineProperty(xFar, name, descFar);
} catch (e) {
console.log(
'Membrane failed to defineProperty',
xNear,
xDescsNear,
e,
);
throw e;
}
});
const xFinished = finish(xFar, xNearRef);
return harden(xFinished);
}
return passNearToFar;
};
const passFunctions = {
passYellowToBlue: makePass(
yellowToBlue,
blueToYellow,
distortYellow,
finishBlue,
'passBlueToYellow',
),
passBlueToYellow: makePass(
blueToYellow,
yellowToBlue,
distortBlue,
finishYellow,
'passYellowToBlue',
),
};
return passFunctions.passBlueToYellow(rootBlue);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment