Use "or" logic with multiple "if case" statements - swift

Suppose I have an enum case with an associated value, and two variables of that enum type:
enum MyEnum {
case foo, bar(_ prop: Int)
}
let var1 = MyEnum.foo
let var2 = MyEnum.bar(1)
If I want to check if both variables match the general case with the associated value, I can do that with a comma:
if case .bar = var1, case .bar = var2 {
print("both are bar")
}
But I need to check if either matches the case, with something like this:
if case .bar = var1 || case .bar = var2 {
print("at least one is bar")
}
However, that doesn't compile. Is there another way to write this to make the logic work?

I would resort to some sort of isBar property on the enum itself, so that the "a or b" test remains readable:
enum MyEnum {
case foo, bar(_ prop: Int)
var isBar: Bool {
switch self {
case .bar: return true
default: return false
}
}
}
let var1 = MyEnum.foo
let var2 = MyEnum.bar(1)
let eitherIsBar = var1.isBar || var2.isBar

You have to implement Equatable protocol for your enum:
extension MyEnum: Equatable {}
func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
switch (lhs, rhs) {
case (let .bar(prop1), let .bar(prop2)):
return prop1 == prop2
case (.foo, .foo):
return true
default:
return false
}
}
Then the following code should work:
if var1 == .bar(1) || var2 == .bar(1) {
print("at least one is bar")
}
UPD: In case if you don't need to check associated value, you can do pattern matching like this:
switch (var1, var2) {
case (.bar, _), (_, .bar): print("at least one is bar")
default: break
}

My solution for this sort of thing is:
if [var1, var2].contains(.bar) {
A lot of the time I want it the other way, a single var that might be filtered based on several values:
if [.bar, .baz].contains(var)
but indeed, if there are associated values involved you have to make them equatable and define an operator.

My guess would be that if case and guard case are syntax sugar, and just a small feature supported by compiler to improve developer’s experience. In the first case you are using consecutive conditions, which is also just a language feature to replace && operator to make the code more readable. When you are using && or || operators, the compiler would expect to get two expressions that return boolean values. case .bar = var1 itself is not exactly an expression that could live alone, without some context in Swift, so it is not treat as an expression that returns a bool.
To summarise:
if case and guard case are simply syntax sugar, works together with if <expression>, <expression> syntax
&& and || are just logical operators that are basically a functions that expect two boolean arguments on both sides.
To solve your problem, use the good old switch statement.
Hope it helps.

Related

Filter array of items by enum property with associated value

class MyClass: Decodable {
let title: String?
let type: MyClass.MyType?
enum MyType {
case article(data: [Article])
case link(data: [LinkTile])
case none
}
}
I would like to filter an array of MyClass items, so the filtered array won't contain instances with type .none
let filteredArray = array.filter { $0.type != .none } // this doesn't work
Unfortunately, you can't use == with enums with associated values. You need to use pattern matching, but that needs to be done in a switch or if statement.
So, that leads to something ugly like this:
let filteredArray = array.filter { if case .none = $0.type! { return false }; return true }
Notes:
You can't name your enum Type because it conflicts with the built-in Type. Change it to something like MyType.
It is terribly confusing to use none as a case in a custom enum because it gets confused (by the humans) with none in an optional. This is made worse by the fact that your type property is optional. Here I have force unwrapped it, but that is dangerous of course.
You could do:
if case .none? = $0.type
This would match the none case explicitly and treat nil as something you want to keep.
To filter out nil and .none, you could use the nil coalescing operator ??:
if case .none = ($0.type ?? .none)
I would suggest declaring type as MyClass.MyType instead of MyClass.MyType?.
I made you a simple example of how to use enum in your context with a filter function.
enum Foo {
case article(data: [Int])
case link(data: [String])
case `none`
static func myfilter(array: [Foo]) -> [Foo]{
var newArray:[Foo] = []
for element in array {
switch element {
case .article(let article):
newArray.append(.article(data: article))
case .link(let link):
newArray.append(.link(data: link))
case .none:
break
}
}
return newArray
}
}
let foo: [Foo] = [.article(data: [1,2,3]), .link(data: ["hello", "world"]), .none]
print(Foo.myfilter(array: foo))
I made a code which you can compile and test, you have to change the type for Foo, articleand link.
When you want to use an enum, you have to use switch case.
If you absolutely want to use the filter in swift you can, but you need to implement the protocol Sequence which is more complicated in this case.
For each case of your enum, you have to manage a case which use the concept of pattern matching. It is very powerful.

Extract associated value from enum regardless of the case in Swift

I want to get the associated value of swift enum object, is there a way to do it shorter/better than in switch statement below?
enum Test {
case a(Int), b(Int), c(Int)
}
func printValue(_ t: Test) {
switch t {
case .a(let v), .b(let v), .c(let v): print("value \(v)")
}
}
Your code for extracting the associated value from multiple enums is the most economical and easy-to-read, there's no need to improve it.
However, the fact that you are looking to extract an associated value regardless of enum's case suggests that you are not using associated values correctly: rather than associating a value with each individual case, you should create a composite type that holds the Int and an enum without an associated value, i.e.
enum Test {
case a, b, c
}
class MyClass {
var num : Int
var tst : Test
}
Now that the associated value is "outside" each enum element, it can be accessed independently of the case, and you can also give it a meaningful name, which adds to readability of your program.
You might want to use mirror type - it's not the better way, but it can be helpful in some cases:
enum Test {
case a(Int), b(Int), c(Int)
}
func printValue(_ t: Test) {
let mirror = Mirror(reflecting: t)
print(mirror.children.first?.value ?? "")
}
printValue(.a(15))
Also using if/case like this, it's a shorter way if you need to extract value only from one case, sometimes it's helpful:
if case .a(let val) = t {
print("value \(val)")
}
Or may be raw value will fit better for your case:
enum Test: Int {
case a = 1
case b = 2
case c = 5
}
func printValue(_ t: Test) {
print("value \(t.rawValue)")
}
printValue(.a)

How to check if enum does not match a pattern?

Note that I have read this post but that post uses a switch statement and it is supposed to do something (return true) when the pattern matches. I, on the other hand, don't want to do anything if the pattern matches and use an if-case statement.
I have this enum:
enum MyEnum {
case a
case b(Int)
case c
case d
}
Here's an instance of it:
let myEnum: MyEnum = .a
Now I want to do something if myEnum is not .b. Since .b has an associated value, I can't simply use an if statement check:
if myEnum != .b { // compiler error
// do my thing here
}
So I must use an if-case statement to pattern match it:
if case .b(_) = myEnum {
} else {
// do my thing here
}
But I really hate the use of the empty if clause. That just looks unswifty to me. I tried to naïvely do this:
if case .b(_) != myEnum { // compiler error!
// do my thing here
}
Is there a better way to do this other than using an empty if clause?
I still have code that should run regardless of whether the pattern matches, so a guard statement won't work.
This is purely minimal semantic change of your own code, but note that you can simply "discard" the empty if clause inline with the case pattern matching:
if case .b(_) = myEnum {} else {
// do your thing here
}
or, leaving out the redundant pattern matching for the associated value of case b:
if case .b = myEnum {} else {
// do your thing here
}
This looks somewhat like a guard clause, but without exiting the scope.
You could use a guard:
guard case .b = myEnum else {
// do your stuff here
return
}
The downside is that you have to exit the scope...
What about:
switch myEnum {
case .b(_):
break
default:
// do your thing here
}
Create a var on the enum which calculates if your value is not .b(_):
enum MyEnum {
case a
case b(Int)
case c
case d
var notB: Bool {
switch self {
case .b(_):
return false
default:
return true
}
}
}
MyEnum.a.notB // true
MyEnum.b(1).notB // false
MyEnum.c // true
MyEnum.d // true
Not the greatest answer since there is still a lot of code to do the check, but at least the check is only one line when you actually use it.
You can write a computed property and have it return a bool value depending on case
enum MyEnum {
case a
case b(Int)
case c
var isCaseB: Bool {
switch self {
case .b(_):
return true
default:
return false
}
}
}
then in your code cleanly check:
if !enumVal.isCaseB {
}
I checked the answer you mentioned in your question but I wasn't sure if you meant you didn't want to use a switch statement at all or just that you didn't want to mix it in with your other code. I think this is a nice and clean way to check prior to writing whatever implementation you need to do depending on the 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
}
}

Enum variables in Swift?

I would like to associate multiple values with an enum value, in a generic way.
This can be done in Java:
enum Test {
A("test", 2);
final String var1;
final int var2;
Test (String var1, int var2) {
this.var1 = var1;
this.var2 = var2;
}
}
public static void main(String []args){
Test test = Test.A;
System.out.println(test.var1);
}
But it looks like it's not possible with Swift? So far, according to docs, there are:
Associated values. Example (from docs):
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
But this is not what I need.
Raw value. Example (from docs):
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
This would be what I need, but it can have only one value!
Is there an elegant solution for this...? Seems like a language design decision, as it would conflict with the associated values concept, at least in the current form. I know I could use e.g. a dictionary to map the enum values to the rest, but really missing to do this in one safe step, like in Java.
For Swift enum, you can only use (String|Integer|Float)LiteralConvertible types as the raw value. If you want to use existing type(e.g. CGPoint) for the raw value, you should follow #Alex answer.
I will provide 2 alternatives in this answer
Very simple solution
enum Test: String {
case A = "foo:1"
case B = "bar:2"
var var1: String {
return split(self.rawValue, { $0 == ":" })[0]
}
var var2: Int {
return split(self.rawValue, { $0 == ":" })[1].toInt()!
}
}
let test = Test.A
println(test.var1) // -> "foo"
You don't like this? go to next one :)
Behavior emulation using struct and static constants
struct Test {
let var1: String
let var2: Int
private init(_ var1:String, _ var2:Int) {
self.var1 = var1
self.var2 = var2
}
}
extension Test {
static let A = Test("foo", 1)
static let B = Test("bar", 2)
static let allValues = [A, B]
}
let test = Test.A
println(test.var1) // -> "foo"
But of course, struct lacks some features from enum. You have to manually implement it.
Swift enum implicitly conforms Hashable protocol.
extension Test: Hashable {
var hashValue:Int {
return find(Test.allValues, self)!
}
}
func ==(lhs:Test, rhs:Test) -> Bool {
return lhs.var1 == rhs.var1 && lhs.var2 == rhs.var2
}
Test.A.hashValue // -> 0
Test.B.hashValue // -> 1
Test.A == Test.B // -> false
In the first code, we already have allValues that is corresponding to values() in Java. valueOf(...) in Java is equivalent to init?(rawValue:) in RawRepresentable protocol in Swift:
extension Test: RawRepresentable {
typealias RawValue = (String, Int)
init?(rawValue: RawValue) {
self.init(rawValue)
if find(Test.allValues, self) == nil{
return nil
}
}
var rawValue: RawValue {
return (var1, var2)
}
}
Test(rawValue: ("bar", 2)) == Test.B
Test(rawValue: ("bar", 4)) == nil
And so on...
I know this is not "in a generic way". And one thing we never can emulate is "Matching Enumeration Values with a Switch Statement" feature in Swift. you always need default case:
var test = Test.A
switch test {
case Test.A: println("is A")
case Test.B: println("is B")
default: fatalError("cannot be here!")
}
Yes it is a design decision but you can kind of work around it in some cases.
The idea is to extend a Type to conform to one of:
integer-literal­ floating-point-literal­ string-literal
The solution can be found here
Bryan Chen's solution:
How to create enum with raw type of CGPoint?
The second solution presented there by Sulthan may also be a way to go for you.
I'm not familiar enough with Swift's history to know if this was possible back when the question was asked. But this is what I would do today in Swift 5.x:
enum Direction {
case north
case east
case south
case west
func name() -> String {
switch self {
case .north: return "North"
case .east: return "East"
case .south: return "South"
case .west: return "West"
}
}
func degress() -> Double {
switch self {
case .north: return 0.0
case .east: return 90.0
case .south: return 180.0
case .west: return 270.0
}
}
}
It retains all the benefits of Swift enums, chief of all, IMO, the ability for the compiler to infer when your code is exhaustive when pattern matching.