Suppose I have:
protocol MyError: Error, Equatable {
var errorDispalyTitle: String { get }
var errorDisplayMessage: String { get }
}
enum ContentState {
case .loading
case .error(any MyError)
case .contentLoaded
}
If i were to implement Equatable
in ContentState
so I can compare during unit tests I end up with a problem because I have to compare two any MyError
types which are boxed and might be of two different underlying types.
extension ContentState: Equatable {
static func == (lhs: ContentState, rhs: ContentState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading):
return true
case (.contentLoaded, .contentLoaded):
return true
case (.error(let lhsError), .error(let rhsError)):
// TODO: have to compare if both underlying types are match and then if they are equal in value
default:
return false
}
}
}
How do I do this?
I tried lifting the generic from the existential type there to the type (e.g. ContentState<Error: MyError>
) which lets it compile when implementing equatable as it knows how to infer the type, but the problem is for whichever class uses that enum it doesnt matter which type is receiving of it, only that is any type of it, and if I don't implement the any
existential it starts requiring the generic to be propagated up the chain.
As of Swift 5.7, Swift automatically “opens” an existential when you pass it as an argument of generic type. The implicit self
argument can be opened (in fact Swift has always opened the self
argument), and Swift can open multiple arguments in a single invocation. So we can write an isEqual(to:)
function that compares any Equatable
to any other Equatable
like this:
extension Equatable {
func isEqual<B: Equatable>(to b: B) -> Bool {
return b as? Self == self
}
}
And then we can complete your ContentState
conformance like this:
extension ContentState: Equatable {
static func == (lhs: ContentState, rhs: ContentState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading):
return true
case (.contentLoaded, .contentLoaded):
return true
case (.error(let lhsError), .error(let rhsError)):
return lhsError.isEqual(to: rhsError)
default:
return false
}
}
}