sjavora
(Å imon Javora)
1
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...
5 Likes
Naxum
(Jake Sawyer)
2
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!
2 Likes
mpangburn
(Michael Pangburn)
3
lancep
(Lance Parker)
4
You can always do
if case .string = a {
print("found a string!")
}
3 Likes
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
}
6 Likes
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 ==.
If the enum is Equatable, this first test works but the second doesn't, maybe it should?
a == .string("hello") // true
a == .string(_) // error: '_' can only appear in a pattern or on the left side of an assignment
To me if a == .string(_) looks so much better than if case .string(_) = a.
Hello. This topic is regularly being discussed. Some of the discussions:
As current workaround, you can use this solution:
But be careful and keep in mind, it is not safe and can be broken in future.
2 Likes
tera
9
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
1 Like
nicodioso
(Nico Dioso)
10
You can create a variable that holds their case names then compare it with each other.
enum Foo {
case string(String)
case number(Int)
private var caseName: String {
switch self {
case .number: return "number"
case .string: return "string"
}
}
func hasSameCase(as type: Self) -> Bool {
return self.caseName == type.caseName
}
}
I think it's easier than using switch self then checking the equality one by one each case
1 Like
Joe_Groff
(Joe Groff)
11
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.
6 Likes
mcritz
(Michael Critz)
12
@nicodioso Thank you so much for this. Someone should pitch this as a language feature so it isnāt buried or hand-rolled.
Button {...}
.disabled(status.isNot(.pending(reason: ""))
is very helpful! If I could add a feature to Swift, it would be something like...
Button {...}
.disabled(status.isNot(.pending))
so we could ignore associated values on enums for the purposes of āsimpleā comparison.