Identity vs Equatable vs Equivalence

The following isn't tied to any specific language or framework, but am curious what others may be doing.


Recently, I've come across more situations where my implementation of Equatable ultimately needs to be split. For example, in unit testing, you want to make sure == operates on all properties in a struct or class (so that XCTAssertEqual can be used). But in production code, you may want == to only take certain properties into account.


I'm thinking of adding another protocol, that when implemented, will provide an operator (e.g. ~=) to signify "equivalence" (i.e. zero or more properties may be ignored). Then, add another assertion function that ultimately calls that operator given two items of a given type that conform to that protocol.


Ultimately, this would provide the following system:


=== for identity

== for Equatable

~= for equivalence


Thoughts?


One area that gets a bit tricky will be when working with sets. e.g. if I wish to maintain a set of items such that I don't want two or more equivalent items in the set, I'd need to do check each item in the set before inserting a new one (by using ~=).

Replies

An "~=" operator already exists — it's the "pattern matching" operator:


developer.apple.com/reference/swift/1851035-swift_standard_library_operators


It's used implicitly by the "switch" statement, to match its argument with cases. (Incidentally, this was a terrible design choice, because you can explicitly override the operator function for specific types, e.g. Int, which changes the meaning of every switch statement on a value of that type. It is trivially easy to create code where, apparently, 1 is equal to 2.)


The other problem with your suggestion, I think, is that you'll find the concept of equivalence won't stay neatly partitioned into a third choice, once you start expanding your range of custome types. Either you won't know how to define equivalence for all contexts, or you'll need multiple equivalence operators.


From this nay-saying perspective, you might do better to reserve XCTAssertEqual for situations where "==" is the correct comparison operator, and assert true/false on an explicit expression in other cases.

Thank you, Quincey. I had spaced on realizing that ~= was already defined. Should I go that route, I'd pick a unique operator. Good to know though about the ability to override that which would lead into issues.


You make a good point about equivalence in general. I think I'd be OK though as for now at least, I'd only implement an equivalence protocol on "leaf" types.

Recently, I've come across more situations where my implementation of Equatable ultimately needs to be split. For example, in unit testing, you want to make sure == operates on all properties in a struct or class (so that XCTAssertEqual can be used). But in production code, you may want == to only take certain properties into account.

I’m somewhat concerned by your problem statement. The

Equatable
protocol has very strict semantic requirements (see the discussion of “substitutability” in the docs) and it seems like your requirements are incompatible with those semantics.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Not to worry, Quinn; all my implementations of Equatable honor what is mentioned in that doc. I've been looking for a good solution to offer another function/operator to aid in situations where two items can be deemed "equivalent".


A simplified example of what I have is a model object that drives user settings. Equatable is used to determine if the user changed anything (i.e. do I need to perform a save operation?). "Equivalence" comes in though when I need to load resources based on those settings, yet the resources only depend upon a subset of the properties on the model object. I was planning on using "equivalence" to see if I can continue to used the currently-loaded resources or not.


Going back to Quincey's post above, it may be that a general solution would open a can of worms. I could always just implement well-named APIs on particular objects. So in the above example, have a 'func shouldReloadResources() -> Bool' that would compare only those specific properties to make its decision.