# 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;
}
```