Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider undefined properties equivalent to missing properties. #21

Merged
merged 1 commit into from
Oct 1, 2020

Conversation

benjamn
Copy link
Owner

@benjamn benjamn commented Oct 1, 2020

Previously, an object a with a key k whose value is undefined (that is, a[k] === void 0) would not be considered deeply equal to another object b that is missing that key, but is otherwise deeply equal to the first object, though a[k] === b[k].

This commit makes these two different kinds of undefined-ness equivalent, by efficiently ignoring undefined-valued keys when comparing objects. This allows more objects than before to be considered equivalent, but the equal(a, b) function still obeys the rules of equivalence relations: reflexivity, symmetry, and transitivity.

This change is similar in spirit to the way we relaxed the strict === requirement for function objects in #12: more functions can be considered equivalent now, but the newly equivalent functions still obey the rules of equality, and thus the change is logically valid, a decision we are free to make but not forced to make.

The additional leniency will be useful in situations where (for example) multiple options objects are combined by application code and then passed to an API, perhaps using ...spread syntax or Object.assign, and the API needs to decide if the new options are different from previous options. This change allows the application code to avoid worrying about the difference between undefined and missing object properties, a discipline that can unnecessarily complicate the way the options need to be combined.

Since the @wry/equality package has not yet reached 1.0.0, this change will be a minor version bump (0.3.0). Extrapolating from the success of #12, I do not anticipate this change will cause problems in practice, but we could (in principle, in the future) consider allowing the equal function to take options that configure how it behaves. If you're reading this because this PR caused you unexpected problems, please let me know below!

Previously, an object `a` with a key `k` whose value is undefined (that
is, `a[k] === void 0`) would not be considered deeply equal to another
object `b` that is missing that key, but is otherwise deeply equal to the
first object, even though `a[k] === b[k]`.

This commit makes these two different kinds of undefined-ness equivalent,
by efficiently ignoring undefined-valued keys when comparing objects. This
allows more objects than before to be considered equivalent, but the
equal(a, b) function still obeys the rules of equivalence relations:
reflexivity, symmetry, and transitivity.

This change is similar in spirit to the way we relaxed the strict ===
requirement for function objects in #12: more functions can be considered
equivalent now, but the newly equivalent functions still obey the rules of
equality, and thus the change is logically valid, a decision we are free
to make but not forced to make.

The additional leniency will be useful in situations where (for example)
multiple options objects are combined by application code and then passed
to an API, perhaps using ...spread syntax or Object.assign, and the API
needs to decide if the new options are the same as previous options. This
change allows the application code to avoid worrying about the difference
between undefined and missing object properties, a discipline that can
unnecessarily complicate the way the options need to be combined.
@benjamn benjamn self-assigned this Oct 1, 2020
Copy link

@hwillson hwillson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great - LGTM!

@benjamn benjamn merged commit d902d88 into main Oct 1, 2020
@benjamn benjamn deleted the consider-undefined-and-missing-properties-equivalent branch October 1, 2020 17:46
benjamn added a commit to apollographql/apollo-client that referenced this pull request Oct 1, 2020
See benjamn/wryware#21 for explanation and
motivation. Should help with #6771, #6803, #6688, #6378, and #7081, and
possibly other regressions that began in @apollo/client@3.1.0.
benjamn added a commit to apollographql/apollo-client that referenced this pull request Oct 1, 2020
See benjamn/wryware#21 for explanation and
motivation. Should help with #6771, #6803, #6688, #6378, and #7081, and
possibly other regressions that began in @apollo/client@3.1.0.
benjamn added a commit that referenced this pull request Sep 15, 2021
Since array equality checking no longer falls through to the object
case, we can preserve the `definedKeys` behavior for objects (introduced
in #21) for arrays, by treating any array holes as undefined elements,
using an ordinary `for` loop. Using `a.every` doesn't work because
`Array` iteration methods like `Array.prototyp.every` skip over holes.
benjamn added a commit that referenced this pull request Jan 21, 2022
Since array equality checking no longer falls through to the object
case, we can preserve the `definedKeys` behavior for objects (introduced
in #21) for arrays, by treating any array holes as undefined elements,
using an ordinary `for` loop. Using `a.every` doesn't work because
`Array` iteration methods like `Array.prototyp.every` skip over holes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants