Error why matching an enumeration using a if statement [duplicate] - swift

This question already has an answer here:
Testing for enum value fails if one has associated value?
(1 answer)
Closed 8 years ago.
I face a problem using enumeration I can't understand.
Here is declaration of an enumeration type:
enum SomeType {
case un
case deux
case trois
}
Then I want to match an individual enumeration values with a if statement:
var testValue: SomeType = .trois
if testValue == .trois {
// Do something
}
Everything is fine!
Now I want to add an associated value only to the first member value:
enum SomeType {
case un(Int)
case deux
case trois
}
var testValue: SomeType = .trois
if testValue == .trois {
// Do something
}
An error than appear on the if statement: Could not find member 'trois'
Does this mean enumerations can only be matched using a switchstatement?
Precisions
What I want to achieve is: "Does testValue is of member value of 'trois' with no consideration for associated value". In others words, how to match an enumeration on member value only.
Here a solution implementing Airspeed Velocity answers:
// Test equality only on member value
func == (lhs:SomeType, rhs:SomeType) -> Bool {
switch (lhs, rhs) {
case (.un(let lhsNum), .un(let rhsNum)):return true
case (.deux, .deux): return true
case (.trois, .trois): return true
default: return false
}
}
// Test equality on member value AND associated value
func === (lhs:SomeType, rhs:SomeType) -> Bool {
switch (lhs, rhs) {
case (.un(let lhsNum), .un(let rhsNum)) where lhsNum == rhsNum: return true
case (.deux, .deux): return true
case (.trois, .trois): return true
default: return false
}
}
var testValue = SomeType.un(3)
// Tests
if testValue == .un(1) {
println("Same member value")
}
if testValue === .un(3) {
println("Same member value AND same associated contents")
}

Enums that don't have associated types are automatically equatable. Enums that have associated types aren't. This makes sense, because only you can know how the associated type (such as the integer that comes with your .un value) should be handled. Even though .trois doesn't have an associated type, the lack of freebie equateableness affects the whole enum. Switch works a little differently, using pattern matching, so it still works.
If you want your enum with an associated type to be equatable, you can define your own == operator:
enum SomeType {
case un(Int)
case deux
case trois
}
// possibly there's a more succinct way to do this switch
func ==(lhs: SomeType, rhs: SomeType) -> Bool {
switch (lhs,rhs) {
case let (.un(i), .un(j)) where i == j: return true
case (.deux,.deux): return true
case (.trois, .trois): return true
default: return false
}
}
var testValue: SomeType = .trois
if testValue == .trois {
println("equals .trois")
}
// note, for SomeType to work with generic
// functions that require Equatable, you have
// to add that too:
extension SomeType: Equatable { }
// which means this will work:
let a: [SomeType] = [.un(1), .deux, .trois]
find(a, .trois)

Related

Swift Enum: Expression pattern matching issue

I have been trying to mix custom associated values with String in an Enum but not able to do so. When I try to apply a switch case over the enum, I get this error: Expression pattern of type 'Fruit' cannot match values of type 'Fruit'
Is it because Strings are value types and hence Swift is able to compare them but not custom class object of Fruit which is a reference type?
class Fruit{
let name: String?
let energyKcl: Double?
let costPerKg: Double?
init(name:String, energyKcl: Double, costPerKg: Double) {
self.name = name
self.energyKcl = energyKcl
self.costPerKg = costPerKg
}
}
enum Calorie {
case fruit(Fruit)
case chocolate (String)
case dairy(String)
case Nuts(String)
}
let banana = Fruit.init(name: "Banana", energyKcl: 100, costPerKg: 10)
func prepareBreakfast(calories: Calorie){
switch calories {
case .chocolate("Dark"):
print("Dark")
case .chocolate("White"):
print("White")
case .fruit(banana): //Error: Expression pattern of type 'Fruit' cannot match values of type 'Fruit'
print("banana")
default:
print ("Not available")
}
}
prepareBreakfast(calories: .fruit(banana))
No the problem is that custom class isn't comparable without Equatable protocol
extension Fruit: Equatable {
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name
&& lhs.energyKcl == rhs.energyKcl
&& lhs.costPerKg == rhs.costPerKg
}
}
Pattern matching uses Equatable internally, so you should change your Fruit class:
extension Fruit: Equatable {
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name // or every field if you want
}
}
If you want to use the reference, simply change the == func to return true if both references are equal, but I don't think it's a good idea:
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs === rhs
}
In your code,
Replace the below line in prepareBreakfast(calories:) method,
case .fruit(banana):
with
case .fruit(let banana):
And you are good to go. I don't think there is any other issue with your code. It is working perfectly fine at my end.

How to do an if-else comparison on enums with arguments [duplicate]

This question already has answers here:
How to compare enum with associated values by ignoring its associated value in Swift?
(9 answers)
Compiler error when comparing values of enum type with associated values?
(2 answers)
Closed 5 years ago.
Language: Swift2.3
For example let's I'll show you different kinds of enums
enum Normal {
case one
case two, three
}
enum NormalRaw: Int {
case one
case two, three
}
enum NormalArg {
case one(Int)
case two, three
}
Switch can be used on all three enums like so:
var normal: Normal = .one
var normalRaw: NormalRaw = .one
var normalArg: NormalArg = .one(1)
switch normal {
case .one: print("1")
default: break
}
switch normalRaw {
case .one: print(normalRaw.rawValue)
default: break
}
switch normalArg {
case .one(let value): print(value)
default: break
}
On the if-else statement though I can only do comparison for Normal and NormalRaw, and an error message shows for NormalArg, so I can't run the code
Binary Operator '==' cannot be applied to operands of type NormalArg
and _
Here's the code example:
if normal == .two { // no issue
.. do something
}
if normalRaw == .two { // no issue
.. do something
}
if normalArg == .two { // error here (the above message)
.. do something
}
if normalArg == .one(_) { // error here (the above message)
.. do something
}
if normalArg == .three { // error here (the above message)
.. do something
}
Any Ideas? I'm not really doing anything with this code, I'm just wondering as to why we can't do comparison.
The trick is to not actually check with == but rather use the case keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let, you get used to it pretty fast:
enum Normal {
case one
case two, three
}
enum NormalRaw: Int {
case one = 1
case two, three
}
enum NormalArg {
case one(Int)
case two, three
}
let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)
if case .one = normalOne {
print("A normal one") //prints "A normal one"
}
if case .one = normalRawOne {
print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}
if case .one(let value) = normalArgOne {
print("A normal \(value)") //prints "A normal 1"
}
The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:
Normal.one == .one //true
Normal.one == .two //false
NormalRaw.one == .one //true
NormalRaw.one == .two //false
NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?
Maybe this makes it clearer why this cannot work out of the box:
class Special {
var name: String?
var special: Special?
}
enum SpecialEnum {
case one(Special)
case two
}
var special1 = Special()
special1.name = "Hello"
var special2 = Special()
special2.name = "World"
special2.special = special1
SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?
So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:
enum NormalArg: Equatable {
case one(Int)
case two
static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
switch (lhs, rhs) {
case (let .one(a1), let .one(a2)):
return a1 == a2
case (.two,.two):
return true
default:
return false
}
}
}
The answer is Equatable Protocol.
Now let's see how it works.
Consider this enum for example:
enum Barcode {
case upca(Int, Int)
case qrCode(String)
case none
}
If we check the equatable operator == on the enum it will fail.
// Error: binary operator '==' cannot be applied to two Barcode operands
Barcode.qrCode("code") == Barcode.qrCode("code")
How to fix this using Equatable Protocol?
extension Barcode: Equatable {
}
func ==(lhs: Barcode, rhs: Barcode) -> Bool {
switch (lhs, rhs) {
case (let .upca(codeA1, codeB1), let .upca(codeA2, codeB2)):
return codeA1 == codeA2 && codeB1 == codeB2
case (let .qrCode(code1), let .qrCode(code2)):
return code1 == code2
case (.None, .None):
return true
default:
return false
}
}
Barcode.qrCode("code") == Barcode.qrCode("code") // true
Barcode.upca(1234, 1234) == Barcode.upca(4567, 7890) // false
Barcode.none == Barcode.none // true

How can I compare two enum instances with NO raw type? [duplicate]

This question already has answers here:
How to test equality of Swift enums with associated values
(15 answers)
Closed 5 years ago.
Given:
enum Example { case Step1 case Step2(data: String) }
and:
let a: Example = .Step1
let b: Example = .Step2(data: "hi")
how do I make this work?
print(a == b) // ERROR: Binary operator '==' cannot be applied to two 'Example' operands
Note that I can't give up the custom enum (it cannot contain raw values)
Implement the Equatable protocol for your enum.
enum Example: Equatable {
case Step1
case Step2(data: String)
static func == (lhs: Example, rhs: Example) -> Bool {
switch(lhs) {
case Step1:
switch(rhs) {
case Step1:
return true
default:
return false
}
case Step2(data: leftString):
switch(rhs) {
case Step2(data: rightString):
return leftString == rightString
default:
return false
}
}
}
}

Cannot compare two enums if there is at least one case with associated value

Suppose I have:
enum Type {
case A
case B
}
Now I can compare them:
let enums = [Type.A, Type.B]
if enums[1] == Type.A { //true }
Everything is fine unless I add another case with associated value:
case C(String)
Now I have an error:
Binary operator == cannot be applied to two Type operands.
How to do this to make it working?
Unfortunately as of Xcode 7 beta 4 you have to implement your own == function yourself since the compiler can no longer infer an equality operation. Especially for Type.C: should it also check for equality of the associated value?
Examples of a logical/static implementation:
// comparing associated values
func ==(t1: Type, t2: Type) -> Bool {
switch (t1, t2) {
case (.A, .A): return true
case (.B, .B): return true
case (.C(let x), .C(let y)) where x == y: return true
default: return false
}
}
// without comparing
func ==(t1: Type, t2: Type) -> Bool {
switch (t1, t2) {
case (.A, .A): return true
case (.B, .B): return true
case (.C, .C): return true
default: return false
}
}
An easier way would be to convert it into a String (using reflection). This would be a dynamic approach/solution but it could change over time especially for own types (no longterm solution):
// you "have to" compare associated values
func ==(t1: Type, t2: Type) -> Bool {
return String(t1) == String(t2)
}
Very easy to do in Swift 4.2
enum SomeType: Equatable {
case a
case b
case c(String)
}
let enums = [SomeType.a, SomeType.b, SomeType.c("Some string")]
if enums[1] == SomeType.b { print("true") }
You'll have to implement func == for the enum type. There are examples on stackoverflow showing you how to do it with one line of code per case.

Can I use the pattern matching operator ~= to match an enum value to an enum type with an associated variable? [duplicate]

This question already has answers here:
How to test equality of Swift enums with associated values
(15 answers)
Closed 7 years ago.
I would like to compare an enum value to an enum type without using switch. The following code, for example, works using the ~= operator:
enum MyEnum {
case A, B
}
let myEnum = MyEnum.A
let isA = myEnum ~= MyEnum.A
isA equals true above.
However, when I try to compare an enum of an enum type with an associated value, like below, I get the compile error Binary operator '~=' cannot be applied to two MyEnum operands.
enum MyEnum {
case A, B(object: Any)
}
let myEnum = MyEnum.A
let isA = myEnum ~= MyEnum.A
Is there a way to work around this error to use the ~= pattern matching operator? Or is my only recourse the following syntax, which in my opinion is significantly more cumbersome:
enum MyEnum {
case A, B(object: Any)
}
let myEnum = MyEnum.A
let isA: Bool
switch myEnum {
case .A:
isA = true
default:
isA = false
}
Thanks in advance for your input and suggestions!
From the documentation for Swift 1.2 "Enumeration case patterns appear only in switch statement case labels". So yes, you need to define your ~= operator (as from the answer pointed in the comments).
In case you just need isA and isB you can implement them using switch combined with _. In your case ~= couldn't work out of the box anyway, since you use an Any associated type, which is not Equatable (i.e.: how can I say if two .B(any) are equal since I cannot say if two any are equal?)
As an example, here is an implementation of from your code that uses String as the associated type. In case you just need the isA and isB, you can still use Any as the associated type without implementing the ~= operator.
enum MyEnum {
case A, B(object: String)
}
let myEnumA = MyEnum.A
let myEnumB = MyEnum.B(object: "Foo")
func ~=(lhs: MyEnum, rhs: MyEnum) -> Bool {
switch (lhs, rhs) {
case (.A, .A):
return true
case let (.B(left), .B(right)):
return left == right
default:
return false
}
}
myEnumA ~= .A // true
myEnumB ~= .B(object: "Foo") // true
myEnumB ~= .B(object: "Bar") // false
func isA(value: MyEnum) -> Bool {
switch value {
case .A:
return true
default:
return false
}
}
func isB(value: MyEnum) -> Bool {
switch value {
case .B(_): // Ignore the associated value
return true
default:
return false
}
}