# R&T / Deep Equality discussion Three contrasting implementations - objects without identity - R&T as primitives - A new integrity level, a new equality operation (not used by existing maps and sets) - A new deep eq. operator ### Question 1: Does a deep equality operator make Records and Tuples better in terms of implementation? Problem 1: if R&T are objects, what happesn to builtin methods? ```javascript! x = [#{a}]; x.includes(#{a}) // if this is an object, it will be false x.includes(#{a}) // if this is a primitive, it will be true ``` `a ~ b, where a = {}, b = {}, will be true` proposal: Change the semantics, to check for immutability Proposal 2: Special casing builtin methods to treat records and tuples specially, feels wrong. We should instead introduce new methods that check deep equality, as they are functionally different. ```javascript! x = [#{a}]; x.includes_deep(#{a}) // if this is an object, it will be true ``` proposal 3: ```javascript! x = [#{a}]; x.includes(#{a}, (a, b) => a ~ b) // if this is an object, it will be true y = [{a}]; y.includes({a}, (a, b) => a ~ b) // if this is an object, it will be true ``` Counter idea: If records and tuples are primitives, then one way we can implement deep equality is by casting to a record or a tuple for various complex types. This is equivalent to ```javascript! x = { [Symbol[@@ToPrimitive]] () { return Record.toRecord(this); } } x === #{} // false, an argument for deep equality operator. x =~= #{} // true Object.deepEqual(x, y) // an alternative syntax ``` ```javascript! Object.deepEqual = function deepEqual(x, y) { return x[@@ToPrimitive](x) == y[@@ToPrimitive](y); } ``` # Idea: Introduce interned objects Atomized objects are extractions of the observable surface area of an object. They are not _equal_ to their source object, they are an _immutable representation of their detectable surface area_. In this case - The R&T syntax becomes a sugar for atomized objects. ## A shim: ```javascript! // hidden object var hashed = {} // Note: this is not complete, obviously. We would have // a different algorithm to do this. This is a short // hand. let PLACEHOLDER_SERIALIZER = JSON.parse; let PLACEHOLDER_IDENTITY_EXTRACTOR = JSON.stringify; // the api function intern(object) { if (object !== Object(object)) { return object; } let identity = PLACEHOLDER_IDENTITY_EXTRACTOR(object); if (hashed[identity]) { return hashed[identity]; } let newObj = Object.create(null) let jsonObj = PLACEHOLDER_SERIALIZER(identity); let props = Object.getOwnPropertyNames(jsonObj); for (let property of props) { newObj[property] = jsonObj[property]; } // Note: in this case, objects are frozen for their // identity to work. However, an alternative is possible. // If the object is modified, it's identity is // different, and it will be a copy. // Immutability is not required for this // to work, but it makes this easier. Object.freeze(newObj); hashed[identity] = newObj; return hashed[identity]; // Note, also not implemented here: we don't do // memoization, but this would be a core part of what is // built in here. } ``` Example with proxies: ```javascript! const target = { message1: "hello", message2: "everyone" }; const handler1 = {}; const proxy1 = new Proxy(target, handler1); atomize(target) === atomize(proxy1); // true ``` What we introduce is not immutability, but object identities tied to structure. https://github.com/PapenfussLab/bionix ### Question 2: Does having a deep equality operator make other things in the language better? - Deep equality is interesting when you are comparing many things, many times. if this is the case, then you can do the atomization technique. - With the toPrimative, it is difficult to tell apart the difference that you would have if the records and tuples proposal went ahead as a primitive. rough syntax ```javascript! a ~ b a ~= b a ==== b //etc. ``` Notes: - Functions in general do not satisfy deep equality - therefore getters do not either. Or any function on an objects as an ownProperty. - This will, as a consequence, disallow proxies. - Deep equality should not have the side effect of creating properties as it is checking. The shim so far ```javascript! function externalEq(a, b) { return someEquality(a, b); } function someEquality(a, b, past = []) { if (past.includes(a)) { if (a === b) { return true; } return false } console.log("comparing ", a, b); if (a !== Object(a)) { if (a === b) { return true } return false } if (a[Symbol["StructEq"]] === b[Symbol["StructEq"]]) { if (a[Symbol["StructEq"]]) { // Callable? return a[Symbol["StructEq"]](b); } } else { return false; } if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { return false; } if (typeof a === "function") { return a === b; } let aProps = Object.getOwnPropertyNames(a); if (aProps.length != Object.getOwnPropertyNames(b).length) { return false; } for (const property of aProps) { if (property === "prototype") { continue; } if (!Object.hasOwn(b, property)) { console.log('different hasown ', b, property) return false } let testedPropA = a[property]; let testedPropB = b[property]; if (testedPropA !== Object(testedPropA)) { if (testedPropA !== testedPropB) { console.log('different prop value: ', testedPropA, testedPropB) return false; } } if (testedPropA === a && testedPropB === b && a === b) { continue; } console.log("object test, ", property); if (property === "constructor") { if (testedPropA !== testedPropB) { return false; } continue; } past.push(a); if (!someEquality(testedPropA, testedPropB, past)) { console.log('structural equality failure: ', testedPropA, testedPropB) return false; } } return true; } ```