Switch testing a non optional against optional case values - swift

I know it is possible to test an optional vs optionals as described in this question: Swift: testing against optional value in switch case
But what I want to do is test a non-optional value with optional case values:
switch nonOptionalView{
case optionalView1:
// Do something
case optionalView2:
// Do something else
}
Xcode returns an error "Value of Optional xxx not unwrapped"
and If I add a "?" at the end of the case statement, I get "? pattern cannot match values of type 'UIView'"

Currently pattern matching optional values isn't supported in the Swift standard library. However, you could easily execute your logic using if/else if statements:
if nonOptionalView === optionalView1 {
// Do something
} else if nonOptionalView === optionalView2 {
// Do something else
}
If you'd really like to be able to pattern match optional values, you can use operator overloading. To overload the pattern matching operator ~=, place the following code in your project:
public func ~=<T : Equatable>(a: T?, b: T?) -> Bool {
return a == b
}
Now you'll be able to pattern match optional values just fine:
switch nonOptionalView {
case optionalView1:
// Do something
case optionalView2:
// Do something else
default:
// Handle default case
}

You could take a look at this solution https://stackoverflow.com/a/26941591/2485238
I find the code is more readable than the if/else solution.
In your case it would be
switch nonOptionalView {
case .Some(optionalView1):
// Do something
case .Some(optionalView2):
// Do something else
default:
// Handle default case
}

Related

Why switch does not accept case nil in optional?

I was working with optional in switch which I noticed switch cannot accept nil as a case!
func test(value: Bool?) {
switch value {
case true: print("true")
case false: print("false")
case nil: print("nil")
}
}
Error: Switch must be exhaustive
then I used default for solving the issue, but I am looking to learn why nil cannot work in first code?
I think this is just an incorrect diagnostic. You can add a default case, and this code will work, but I'm actually surprised that works.
You see, the value you're switching on is a Bool?, but 2 of the values you're comparing it against are true and false. The typical approach for this is to use pattern matching:
func test(value: Bool?) {
switch value {
case .some(true): print("true")
case .some(false): print("false")
case .none: print("nil")
}
}
This works correctly, but it's a bit wordy. Luckily, Swift has a syntactic sugar for pattern matching against optionals, ? and nil:
func test(value: Bool?) {
switch value {
case true?: print("true")
case false?: print("false")
case nil: print("nil")
}
}
Both of these snippets compile and work fine. It does open a new question as to why your original cases worked, if only you added the default.
I suggest you post about this on Swift forums, to get more input from the compiler nerds :)
An Optional is an enum with two cases, one which has the value associated: either .some(value) or .none
The correct way to use a switch for pattern matching an optional is:
switch optional {
case .some(let value): // do your stuff with value
case .none: // do your stuff when the optional is nil
}
Alternatively you could also use map on an optional to execute a closure when its value is not nil:
optional.map { value in
// do your stuff with the value
}
With this approach the body closure passed to the map function only executes when the optional is not nil at runtime, otherwise it doesn’t executes.

Extract value in Swift switch

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
}

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 make a class that participates in "if let" constructs the way the Optional does?

I can use if let construct to unwrap optionals, like this:
if let x = someOptional {
// x represents the value of someOptional, and is known to be non-nil
}
In addition to doing the unwrapping, Swift compiler knows to treat the let x = someOptional construct as a logical value.
Is the logic for Optional built into the compiler itself, or is it possible to define my own class with the same behavior?
In other words, can I write something like this:
class MyLogical : SomeProtocolForSwiftToKnowHowToTreatMyClassAsBool {
...
var boolValue : Bool {
return ... // true or false, depending on the state of my object
}
}
and have Swift treat it like a logical value that I can put inside an if condition, with or without let?
if let x = getMyLogical() {
assert(x.boolValue) // boolValue must evaluate to true to get here
}
Note: I remember there being a LogicalValue protocol in an earlier version of Swift, and Optional used to conform to that protocol. LogicalValue is gone now, and there is no replacement (BooleanType has the boolValue property now, but Apple discourages conformance to the BooleanType protocol unless your class represents a simple Boolean value).
Swift's powerful pattern matching system makes something close to this very possible. The case let... pattern does much of what you propose:
if case let x = getMyLogical() where x.boolValue {
assert(x.boolValue) // boolValue must evaluate to true to get here
}
It works with guard statements too:
guard case let x = getMyLogical() where x.boolValue else { fatalError() }
assert(x.boolValue) // boolValue must evaluate to true to get here
In other words, the case let... syntax can be used to conditionally bind any value to any name for use in an if block or after a guard statement.
And I suppose for completeness, it works inside a switch block as well:
switch getMyLogical() {
case let x where x.boolValue:
assert(x.boolValue) // boolValue must evaluate to true to get here
default:
break
}
And as the logical test for a loop:
while case let x = getMyLogical() where x.boolValue {
assert(x.boolValue) // boolValue must evaluate to true to get here
}
For a long time I thought switch statements were the only place you could use case let... But they can be used in other places, too.
I'll add one final note. The case let... pattern can even be used to unwrap optionals:
let num: Int? = 42
if case let x? = num {
print(x)
}
The if let... syntax for unwrapping optionals appears to be a special case of the general case let... pattern matching syntax, an added bit of syntactic sugar to make optionals even easier to work with, but not fundamentally different than the more general case let... functionality.

Swift switch statements need to be exhaustive? [duplicate]

The Apple documentation says
Every switch statement must be exhaustive. That is, every possible
value of the type being considered must be matched by one of the
switch cases.
So in new Xcode I have placed a code like this
println(UInt16.min); // Output : '0'
println(UInt16.max); // Output : '65535'
var quantity : UInt16 = 10;
switch quantity {
case 0...65535: //OR case UInt16.min...UInt16.max:
println();
default:
println();
}
Now if i remove the default section I get a compiler error:
Switch must be exhaustive
Do you want to add missing cases? Fix
So my question is for a case that I have mentioned as case 0...65535: have I not mentioned all the case values for an UInt16 ?? But still I am getting an error ?? Why am I getting this error, Did i miss something ??
Swift only truly verifies that a switch block is exhaustive when working with enum types. Even a switching on Bool requires a default block in addition to true and false:
var b = true
switch b {
case true: println("true")
case false: println("false")
}
// error: switch must be exhaustive, consider adding a default clause
With an enum, however, the compiler is happy to only look at the two cases:
enum MyBool {
case True
case False
}
var b = MyBool.True
switch b {
case .True: println("true")
case .False: println("false")
}
If you need to include a default block for the compiler's sake but don't have anything for it to do, the break keyword comes in handy:
var b = true
switch b {
case true: println("true")
case false: println("false")
default: break
}
Part of why you see that error because the compiler can't verify that switch is exhaustive without running code. The expression 0...65535 creates a ClosedInterval struct, and when the switch statement executes it has to ask that struct if the value quantity is in the interval. There's room for that to change at run time, so the compiler can't check it at compile time. (See the Halting Problem.)
More generally, the compiler can't detect an exhaustive switch for integer values — even if you add specific cases for every integer value (case 0: ... case 1: ... ... case 65535:), it doesn't know your switch is exhaustive. (Theoretically it could, though: consider filing a feature request about this if it's something you'd like to see.)
As it stands, there are two scenarios where Swift can detect completeness and allow you to omit the default clause: enums and value binding in tuples. #NateCook's answer covers enums — if you switch on an enum value and have a case in your switch for every case in the enum, you don't need a default. You also don't need a default label if you switch on a tuple and bind every possible combination of values, as seen in the Swift book:
switch anotherPoint {
case (let x, 0):
println("on the x-axis with an x value of \(x)")
case (0, let y):
println("on the y-axis with a y value of \(y)")
case let (x, y):
println("somewhere else at (\(x), \(y))")
}
You might generalize this rule as "if the type system knows about the possible values of your type, it can detect switch completeness", but the fact that there's a level on which the type system doesn't know the range of possible (e.g.) UInt32 values is sort of splitting hairs...
Swift 4.1. Either you need to specify all cases or Just include default block inside switch statement.
(As of Swift 4.2, and probably earlier): I have a helper function that converts a Bool? into the selectedSegmentIndex for a UISegmentedControl with 2 segments. If the value is nil then neither segment should be selected. My function uses a switch, which returns the appropriate segment index for true or false values, and uses this to explicitly test for the nil and satisfy the compiler's need for it to be exhaustive:
case nil: // only remaining possible value
fallthrough
default:
return UISegmentedControl.noSegment
Technically, the case nil: fallthrough isn't required because the default: will suffice, but this syntax may be useful if you want to explicitly test a value to make the code more self-documenting, or perhaps in another situation.
Check if your enum was initialised as an optional which could be either case or nil