Reputation: 2253
xCode 10.2.1, Swift language unspecified (4, 4.2, 5)
I have two double optional bools. I would like to unwrap them in the switch statement as so..
func evaluate() -> Bool? {
let lhs = try? getLhs() // lhs = Bool??; getLhs() returns Bool?
let rhs = try? getRhs() // rhs = Bool??; getRhs() returns Bool?
switch (lhs ?? nil, rhs ?? nil) {
case (nil, nil):
return nil
case (nil, rhs):
return rhs ?? nil
case (lhs, nil):
return lhs ?? nil
default:
guard case let lhs?? = lhs, case let rhs?? = rhs else { return nil }
return lhs || rhs
}
}
This works, but I'm curious why I need to double unwrap the default case? I was thinking once the lhs and rhs get unwrapped from the switch inputs, the default case would work with a Bool? for lhs and rhs.
Update --
Thanks for answering. Yes, the double optional is overkill, but what I really wanted to understand was why my default was requiring me to unwrap again. I did not completely understand how switch works with optionals. Alexander helped me understand all the iterations and vacawama gave me a clean implementation.
Upvotes: 0
Views: 1177
Reputation: 154583
This works, but I'm curious why I need to double unwrap the default case? I was thinking once the lhs and rhs get unwrapped from the switch inputs, the default case would work with a Bool? for lhs and rhs.
You aren't changing lhs
and rhs
when you do lhs ?? nil
and rhs ?? nil
. You are creating new values. So when you get to the default
case, lhs
and rhs
are still Bool??
. You can use let lhs
and let rhs
to capture the unwrapped values as I did in my solution below.
Here is another way to do it. A bit cleaner with some pattern matching:
switch (lhs as? Bool, rhs as? Bool) {
case (nil, nil):
return nil
case (nil, let rhs):
return rhs
case (let lhs, nil):
return lhs
case (let lhs?, let rhs?):
return lhs || rhs
}
Explanation
Casting the Bool??
with as? Bool
leaves you with a Bool?
. The let rhs
and let lhs
in the pattern matching catch the Bool?
value so that it can be returned. In the final case
, let lhs?
and let rhs?
unwrap the Bool?
values to get Bool
values so that ||
can be performed.
Test Cases
test(nil, nil) // nil
test(nil, false) // false
test(nil, true) // true
test(nil, Optional(nil)) // nil
test(nil, Optional(false)) // false
test(nil, Optional(true)) // true
test(false, nil) // false
test(false, false) // false
test(false, true) // true
test(false, Optional(nil)) // false
test(false, Optional(false)) // false
test(false, Optional(true)) // true
test(true, nil) // true
test(true, false) // true
test(true, true) // true
test(true, Optional(nil)) // true
test(true, Optional(false)) // true
test(true, Optional(true)) // true
test(Optional(nil), nil) // nil
test(Optional(nil), false) // false
test(Optional(nil), true) // true
test(Optional(nil), Optional(nil)) // nil
test(Optional(nil), Optional(false)) // false
test(Optional(nil), Optional(true)) // true
test(Optional(false), nil) // false
test(Optional(false), false) // false
test(Optional(false), true) // true
test(Optional(false), Optional(nil)) // false
test(Optional(false), Optional(false)) // false
test(Optional(false), Optional(true)) // true
test(Optional(true), nil) // true
test(Optional(true), false) // true
test(Optional(true), true) // true
test(Optional(true), Optional(nil)) // true
test(Optional(true), Optional(false)) // true
test(Optional(true), Optional(true)) // true
Upvotes: 2
Reputation: 63167
Bool
has 2 values:
true
false
Bool?
has 3 values, .none
(a.k.a. nil
), and one .some
case wrapping each of the 2 possible cases of Bool
:
.some(true)
.some(false)
.none
Bool??
has 4 values, .none
, and one .some
case wrapping each of the 3 possible cases of Bool?
:
.some(.some(true))
.some(.some(false))
.some(.none)
.none
To make a first approximation of a solution, we can enumerate all possible values:
func getLhs() throws -> Bool? { return nil }
func getRhs() throws -> Bool? { return nil }
func f() -> Bool? {
let lhs: Bool?? = try? getLhs()
let rhs: Bool?? = try? getRhs()
switch (lhs, rhs) {
case (.some(.some(true)), .some(.some(true))): return true
case (.some(.some(true)), .some(.some(false))): return true
case (.some(.some(true)), .some(.none)): return true
case (.some(.some(true)), .none): return true
case (.some(.some(false)), .some(.some(true))): return true // result of OR
case (.some(.some(false)), .some(.some(false))): return false
case (.some(.some(false)), .some(.none)): return false
case (.some(.some(false)), .none): return false
case (.some(.none), .some(.some(true))): return true
case (.some(.none), .some(.some(false))): return false
case (.some(.none), .some(.none)): return nil
case (.some(.none), .none): return nil
case (.none, .some(.some(true))): return true
case (.none, .some(.some(false))): return false
case (.none, .some(.none)): return nil
case (.none, .none): return nil
}
}
So now this is obviously crazy, but we can collapse cases by binding variables:
switch (lhs, rhs) {
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.none)): return l
case let (.some(.some(l)), .none): return l
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.none)): return l
case let (.some(.some(l)), .none): return l
case let (.some(.none), .some(.some(r))): return r
case let (.some(.none), .some(.some(r))): return r
case (.some(.none), .some(.none)): return nil
case (.some(.none), .none): return nil
case let (.none, .some(.some(r))): return r
case let (.none, .some(.some(r))): return r
case (.none, .some(.none)): return nil
case (.none, .none): return nil
}
And we can remove the duplicate cases:
switch (lhs, rhs) {
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.none)): return l
case let (.some(.some(l)), .none): return l
case let (.some(.none), .some(.some(r))): return r
case (.some(.none), .some(.none)): return nil
case (.some(.none), .none): return nil
case let (.none, .some(.some(r))): return r
case (.none, .some(.none)): return nil
case (.none, .none): return nil
}
We can then group all the nested nil
cases:
switch (lhs, rhs) {
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.none)): return l
case let (.some(.some(l)), .none): return l
case let (.some(.none), .some(.some(r))): return r
case let (.none, .some(.some(r))): return r
case (.some(.none), .some(.none)),
(.some(.none), .none),
(.none, .some(.none)),
(.none, .none):
return nil
}
We can then just catch all those in a default
:
switch (lhs, rhs) {
case let (.some(.some(l)), .some(.some(r))): return l || r
case let (.some(.some(l)), .some(.none)): return l
case let (.some(.some(l)), .none): return l
case let (.some(.none), .some(.some(r))): return r
case let (.none, .some(.some(r))): return r
default: return nil
}
Why do you have a function that throws
AND returns an Optional
? It would be much better to make the function return a non optional bool, and have the nil
cases handled by one of the exceptions. Alternatively, you could return a Result<Bool, Error>
, where one of the error cases encoded the nil
.
If you had to stick with these types, a switch
is the wrong way to go about it entirely. You could use Optional.map
or Optional.flatMap
, but those would get hairy too.
Upvotes: 2