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.
Related
Optional in Swift allows for magic where switch-ing over an optional enum flattens the cases into a single switch statement.
Given:
enum Foo {
case bar, baz
}
You can:
let foo: Foo? = .bar
switch foo {
case .bar:
break
case .baz:
break
case nil:
break
Here, Optional is an enum and Foo is an enum, but just one single statement is enough to cover all cases of both.
Question: can I declare my own enum inside another enum so that the cases can be handled in a flat way too?
So that I could:
enum Foo<Bar> {
case nope
case dope(Bar)
}
enum Baz {
case yep
}
let b: Foo<Baz> = .dope(.yep)
switch b {
case .nope:
break
case .yep:
break
}
Maybe if I call the case Foo.dope as Foo.some? Maybe there is an annotation that I can use?
You could do this with …
switch b {
case .nope: // stuff
case .dope(.yep): // other stuff
case .dope(.someOtherCase): // more stuff
}
Responding to your comment…
Imagine you could flatten it like you said. How would you deal with…
enum Foo {
case a
case b(Bar)
case c(Bar)
}
If you are allowed to exclude the .b from the switch then there is no way to differentiate between .b and .c.
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.
I would like to make a switch statement on Type of given protocol. Lets say I have:
protocol A {}
struct SA: A {}
struct SB: A {}
let aself: A.Type = SA.self
let bself: A.Type = SB.self
switch aself {
case is SA:
break
default:
break
}
Can I somehow make such switch work? This gives me warning that such conversion always fails. Can this be done somehow?
You can write something like this:
switch aself {
case is SA.Type:
print("is SA.Type")
default:
print("Unknown type")
}
Or this:
switch ObjectIdentifier(aself) {
case ObjectIdentifier(SA.self):
print("is SA.Type")
default:
print("Unknown type")
}
(Some code explaining different behavior for subclasses as I commented below.)
class CA: A {}
class CAA: CA {}
let caaself: A.Type = CAA.self
switch caaself {
case is CA.Type:
print("is CA.Type") //->is CA.Type
default:
print("Unknown type")
}
switch ObjectIdentifier(caaself) {
case ObjectIdentifier(CA.self):
print("is CA.Type")
default:
print("Unknown type") //->Unknown type
}
You may need to use ObjectIdentifier when you want to exclude subclasses in your type matching. (There may be other ways, but I do not think of any, now.)
I assume that you'll also want to use the casted type value, in this situation you can use some pattern matching:
switch aself {
case let a as SA.Type:
break
default:
break
}
If you don't want to use the casted value:
switch aself {
case let _ as SA.Type:
break
default:
break
}
Is it possible in Swift to pattern match and extract the initial value (now cast) at the same time?
For example, if I had these enums:
enum Inner {
case a(Int)
case b(Int)
}
enum Outer {
case one
case two(Inner)
}
and I wanted to match Outer.two(Inner.a(1)) and have have a variable cast to that at the same time
let value: Any // Could be anything :|
switch value {
case let a as Outer.two(Inner.a(1)):
// Do something which needs `a`
default"
// Do some other things
}
Obviously, that doesn't work because as only casts to types.
I've also tried
case Outer.two(let a) where a == Inner.a(1):
case let Outer.two(.a(1)) = a: // i.e. the syntax in "guard case let ..."
which don't work. (NB The first would work, but implementing == isn't an option for me, annoyingly)
FYI: Scala lets you do it with the # operator, something like this:
case a # Outer.two(Inner.a(1)):
Is there a syntax which can do this, or do I need to rethink how I'm solving my problem?
I don't think there is an equivalent pattern to
case a # Outer.two(Inner.a(1)): in Swift. Here is a possible
workaround:
switch value {
case Outer.two(Inner.a(1)):
let a = Inner.a(1)
// Do something which needs `a`
default:
// Do some other things
}
class MyClass
{
enum MyEnum {
case FirstCase
case SecondCase(Int)
case ThirdCase
}
var state:MyEnum!
func myMethod ()
{
if state! == MyEnum.FirstCase {
// Do something
}
}
}
I get the compiler error pointing at the if statement::
Binary operator '==' cannot be applied to two 'MyClass.MyEnum'
operands
If instead, I use a switch statement, there is no problem:
switch state! {
// Also, why do I need `!` if state is already an
// implicitly unwrapped optional? Is it because optionals also
// are internally enums, and the compiler gets confused?
case .FirstCase:
// do something...
default:
// (do nothing)
break
}
However, the switch statement feels too verbose: I just want to do something for .FirstCase, and nothing otherwise. An if statement makes more sense.
What's going on with enums and == ?
EDIT: This is ultra-weird. After settling for the switch version and moving on to other (totally unrelated) parts of my code, and coming back, the if-statement version (comnparing force-unwrapped property against fixed enum case) is compiling with no errors.
I can only conclude that it has something to do with some corrupted cache in the parser that got cleared along the way.
EDIT 2 (Thanks #LeoDabus and #MartinR): It seems that the error appears when I set an associated value to the other enum case (not the one I am comparing against - in this case, .SecondCase). I still don't understand why that triggers this compiler error in particular ("Can't use binary operator '=='..."), or what that means.
As you said in a comment, your enumeration type actually has associated
values. In that case there is no default == operator for the enum type.
But you can use pattern matching even in an if statement (since Swift 2):
class MyClass {
enum MyEnum {
case FirstCase
case SecondCase
case ThirdCase(Int)
}
var state:MyEnum!
func myMethod () {
if case .FirstCase? = state {
}
}
}
Here .FirstCase? is a shortcut for .Some(MyEnum.FirstCase).
In your switch-statement, state is not automatically unwrapped,
even if it is an implicitly unwrapped optional (otherwise you could
not match against nil). But the same pattern can be used here:
switch state {
case .FirstCase?:
// do something...
default:
break
}
Update: As of Swift 4.1 (Xcode 9.3) the compiler can synthesize conformance to Equatable/Hashable for enums with associated values (if all their types are Equatable/Hashable). It suffices to declare the conformance:
class MyClass {
enum MyEnum: Equatable {
case firstCase
case secondCase
case thirdCase(Int)
}
var state:MyEnum!
func myMethod () {
if state == .firstCase {
// ...
}
}
}
class MyClass {
enum MyEnum {
case FirstCase
case SecondCase
case ThirdCase
}
var state: MyEnum!
func myMethod() {
guard let state = state else { return }
if state == MyEnum.FirstCase {
// Do something
print(true)
} else {
print(false)
}
}
}
let myClass = MyClass()
myClass.state = .FirstCase
myClass.myMethod()
myClass.state = .SecondCase
myClass.myMethod()