On Swift enums with associated value equality

It just began with a simple Tweet from Cocoanetics  :

Is there a better way to make an enum with associated values equatable?

Most of responses were pure technical (but valid) answers. In short : “you should use a switch, but still need to compare all cases”.

Ok. But the code above still feels wrong to me because he tries to make two different cases equatable just because they have the same associated value. With such a case .afterID(42) == .beforeID(42) would be true and this can be dangerous.

Maybe not immediately but in the future, when this particularity will be forgotten and when the developer would rely on a regular Equatable implemetation where two different cases can’t be equal.

How could we improve this code with this two requirements : 
 • Two different cases can’t be equal.
 • Identifiers of .afterID(_) and .beforeID(_) equality must be easy and straightforward to check.

An easy first attempt could be to perform a strict equality check on cases, and use an identifier property to compare check identifiers equality :

And it looks great :

QF.afterID(42) == QF.beforeID(42) // false
QF.afterID(42).identifier == QF.beforeID(42).identifier // true \o/

But the problem is that it can lead to strange results :

QF.noFilter.identifier == QF.offset(42).identifier // true

So how we could do that without this strange behaviours ?


Let me introduce you an amazing, underused, Swift Enum feature available since Swift 2 : indirect case

An indirect case allows you to make a recursive enum by associating a case to another case of the same enum.

But how this could solve our problem ? Well, it’s quite simple : instead of having identifier being an Int?, this var can be itself a QueryFilter with an indirect case shadowing the original QueryFilter.

This way we can make a specific rule for shadowed cases in our == implementation.

We could also rename this specific identifier used by only 50% of our cases into a more generic value.

But a sample code is better than a long explanation :

Does it work well ? Hell yes ! 😈

QF.noFilter == QF.noFilter   // true
QF.noFilter == QF.offset(42) // false
QF.afterID(42) == QF.afterID(42)   // true
QF.afterID(42) == QF.afterID(1337) // false
QF.afterID(42) == QF.beforeID(42)   // false
QF.afterID(42) == QF.beforeID(1337) // false
QF.noFilter.value == QF.noFilter.value   // true
QF.noFilter.value == QF.offset(42).value // false
QF.afterID(42).value == QF.afterID(42).value   // true
QF.afterID(42).value == QF.afterID(1337).value // false
QF.afterID(42).value == QF.beforeID(42).value   // true
QF.afterID(42).value == QF.beforeID(1337).value // false
🎉🎉🎉

As I said, I think that indirect enums are an underused Swift feature, and I saw only few opportunities to use it since it’s available.

What about you ? Have you ever used indirect enums in Swift ? And for what ?