Multiple times I have now wanted to compare two values of an enum while ignoring their respective associated values.
enum Foo {
case string(String)
case number(Int)
}
let a = Foo.string("str")
a == Foo.string // Binary operator '==' cannot be applied to operands of type 'Foo' and '(String) -> Foo'
I understand why this doesn't work, but... I really want it to! Looking around the web for workarounds has led me to Erica Sadun's article, an interesting gist from the comments of said article, and even a post from Swift 1.2 era asking basically the same question.
All the solutions I've seen so far either require me to manually write type-specific code, or are plain ugly. Even if there are workarounds, though, I'd love for the following to work as well:
foos.contains(.number)
...which could only work if the cases without their associated values were somehow Equatable.
I suppose I'm posting this here out of frustration and hoping that perhaps someone from the Swift team will chime in, because this feels like it should work and just doesn't...
My only use of Sourcery is to auto-generate a subtype on my associated value enums so I can do simple == and != checks for their case names. I would love to see this feature exist in Swift.
It should be easy to do things like the following without resorting to weird quirks or a bunch of extra code
enum State {
case ready
case loading(reasons: [String])
}
let stateA = State.ready
let stateB = State.loading(reasons: ["ReasonA", "ReasonB"])
print(stateA.case == stateB.case) //false
if stateA.case != .loading {
// do something
}
There are a bunch of hiccups when writing if case let something = .enumCase (including the inability to use != with case let).
I too would love to see it simplified!
I think that, similar to SE-0194, the best way to provide this feature would be to add a CaseDiscriminatable (to be bike-shedded) protocol, for which, like CaseEnumerable a compiler-provided default implementation would be created for same-file extensions.
e.g:
protocol CaseDiscriminatable {
associatedtype Discriminator: Hashable
var case: Discriminator
}
Here's a simple (if slightly verbose) way to do it:
enum Foo {
case string(String)
case number(Int)
func hasSameCaseAs(_ type: Self) -> Bool {
switch self {
case .string: if case .string = type { return true }
case .number: if case .number = type { return true }
}
return false
}
}
var a = Foo.string("hello")
var b = Foo.number(6)
var c = Foo.number(5)
a.hasSameCaseAs(b) // returns false
b.hasSameCaseAs(c) // returns true
a.hasSameCaseAs(Foo.string("")) // returns true
Doesn't use the == operator but it's not really ==, is it?
As an aside, I'm unhappy with the form: if case .string = type { return true } too since its not an assignment but a comparison and would be better served by ==.
this works now without brackets and Equatable requirement:
if case .string = a {
if to choose between:
if a == .string { // proposed
if .string == a { // same
vs
if case .string = a { // existing
then +1 for the former form. interestingly Equatable requirement can be optional:
enum E {
case string(String)
case red
case green
case blue
}
if a == .string { // proposed: ok
if .string == a { // same
if a == .string("hello") { // error, 'Equatable' required
if a == .red { // error, 'Equatable' required
This might be an interesting use case for a macro, which could generate a nested Cases enum with one case for each case in the outer enum but without the payloads, along with a computed property that maps the payload cases in the outer enums to the corresponding no-payload cases in the inner enum.