Skip to content

Instantly share code, notes, and snippets.

@js-choi
Last active July 8, 2022 19:42
Show Gist options
  • Save js-choi/b8b1a1c1388354d1b4384bea8a1fca0a to your computer and use it in GitHub Desktop.
Save js-choi/b8b1a1c1388354d1b4384bea8a1fca0a to your computer and use it in GitHub Desktop.

This proposal introduces Object.equiv, Object.diff, and Symbol.diff.

Object.equiv would essentially be:

function equiv (objectA, objectB) {
  for (const d of Object.diff(objectA, objectB)) {
    return false;
  }

  return true;
}
class Rectangle {
  constructor (height, weight) {
    this.height = height;
    this.width = width;
  }

  * [Symbol.diff] (other) {
    // Check for identical constructors.
    if (!(other instanceof this.constructor)) {
      return yield {
        op: 'replace',
        from: this,
        value: other,
      };
    }
    
    // Check for identical heights.
    if (this.height !== other.height) {
      return yield {
        op: 'replace',
        path: [ 'height' ],
        from: this.height,
        value: other.height,
      };
    }

    // Check for identical widths.
    if (this.width !== other.width) {
      return yield {
        op: 'replace',
        path: [ 'width' ],
        from: this.width,
        value: other.width,
      };
    }
}

class ArrayLike {
  constructor (length) {
    this.length = length;
  }

  * [Symbol.diff] (other) {
    // Check for identical constructors.
    if (!(other instanceof this.constructor)) {
      return yield {
        op: 'replace',
        from: this,
        value: other,
      };
    }
    
    // The overlap between this and the other ArrayLike is the shorter of their
    // lengths.
    const overlapLength = Math.min(this.length, other.length);
    
    // Check for equivalent elements in their overlaps.
    for (let i = 0; i < overlapLength; i ++) {
      // If there are any differences between the two i-th elements, then yield
      // those differences (prepending the i to the differences’ paths).
      for (const { op, path, from, value } of Object.diff(this[i], other[i]);
        yield {
          op,
          path: [ i, ...path ],
          from,
          value,
        };
      }
    }

    // Any remaining elements in this ArrayLike has been “removed” from the
    // other ArrayLike.
    for (let i = overlapLength; i < this.length; i ++) {
      yield {
        op: 'remove',
        path: i,
        from: this[i],
      };
    }

    // Any remaining elements in the other ArrayLike have been “added” from the
    // other ArrayLike.
    for (let i = overlapLength; i < other.length; i ++) {
      yield {
        op: 'add',
        path: i,
        value: other[i],
      };
    }
  }
}

Two rectangles that are equivalent:

Array.from(Object.diff(
  new Rectangle(0, 0),
  new Rectangle(0, 0),
)

[]

A rectangle and a plain object:

Array.from(Object.diff(
  new Rectangle(0, 0),
  {},
)

[ { op: 'replace', from: theRectangle, value: thePlainObject } ]

Two rectangles that differ in their widths:

Array.from(Object.diff(
  new Rectangle(0, 0),
  new Rectangle(0, 1),
)

[ { op: 'replace', path: [ 'width' ], from: 0, value: 1 } ]

Two ArrayLikes that differ in length by 2:

Array.from(Object.diff(
  new ArrayLike(2),
  new ArrayLike(4),
)

[ { op: 'add', path: [ 2 ], value: undefined },
  { op: 'add', path: [ 3 ], value: undefined } ]

Two length-1 ArrayLikes with Rectangles that differ in their width:

const a0 = new ArrayLike(1);
const a1 = new ArrayLike(1);
a0[0] = new Rectangle(0, 0);
a0[0] = new Rectangle(0, 1);
Array.from(Object.diff(a0, a1)

[ { op: 'replace', path: [ 0, 'width' ], old: 0, value: 1 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment