Refer to the value of switch statement in a branch - swift

Is there a way in Swift to refer to the value I am switching on in a branch? For example:
switch UIDevice.current.userInterfaceIdiom {
// cases
default:
fatalError("User interface idiom \(value) is not supported")
}
I am thinking about something similar to the implicit error reference inside catch block:
do {
// ...
} catch {
print(error) // 'error' is defined implicitly
}
Of course, I can create a variable myself, but I'd like to avoid that.

There is no built-in variable, but you can easily capture the value yourself with the case let pattern:
switch UIDevice.current.userInterfaceIdiom {
// cases
case let value:
fatalError("User interface idiom \(value) is not supported")
}
Note: This will match anything, so it replaces the default case and it should be the last case in your switch.
You can use a where clause to allow the capture of values other than everything:
switch 1 + 2 * 3 {
case let value where 0...9 ~= value:
print("The value \(value) is a single digit value")
case let value:
print("\(value) is not a single digit value.")
}
but it would be easier in that case just to assign the value to the variable before the switch.

Related

Use Guard to trap all enum states other than one

Hitting a block on using a guard. Ideally want to do a guard statement that traps all enum states other than one, so would be something like:
guard case .notInUse != foundTransition.validToObject.isInSituation else {
fatalError("Transition: The toObject is already in a situation")
}
But this non matching test does not seem to be allowed. So instead using the below if statement:
if case .notInUse = foundTransition.validToObject.isInSituation {} else {
fatalError("Transition: The toObject is already in a situation")
}
It works but feels a guard would be neater. Any ideas?
It is impossible to negate a case statement.
You either need to use your if statement, or make the enumeration Equatable, in which case you would just drop the case keyword.
guard foundTransition.validToObject.isInSituation != .notInUse
Alternatively, you can use a guard statement that is backed by a switch or if. But you'll never get rid of them! 😈
guard ({
if case .notInUse = foundTransition.validToObject.isInSituation {
return false
}
return true
} ()) else {
fatalError("Transition: The toObject is already in a situation")
}

Unwrapping associated value for all cases in switch

I have an enum similar to this, where all cases contain the same associated value content:
enum RowType {
case single(_ content: [Any])
case double(_ content: [Any])
case triple(_ content: [Any])
...
}
I know I could just use a struct with a rowType and a content attribute, but let's please not discuss this, but rather have a look at the following:
When I want to switch all the cases, I could of course do this:
switch row {
case .single(let content):
// do anything
break
case .double(let content):
// ...
...
}
or even:
switch row {
case .single(let content), .double(let content), ...:
// do the same thing for all cases
break
}
Now my enum contains a few more cases and is likely to grow further while under development, so it's inconvenient for me to list all cases in the same case statement just to unwrap the content argument.
So I got curious and wondered: Can I somehow "wildcard" the enum case itself and still unwrap the content field? Like a default case with associated value...
I was hoping to be able to do something like ...
switch row {
case _(let content):
// do something
break
}
... or maybe accessing the associated value in the default case.
I did a bit of research but couldn't find an answer, so I'm excited for your thoughts.
Try this in Playground.
enum RowType {
case single(_ content: [Any])
case double(_ content: [Any])
case triple(_ content: [Any])
case noVal
var associatedValue: Any? {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return associated.value
}
print("WARNING: Enum option of \(self) does not have an associated value")
return nil
}
}
}
let row : RowType = .double([1,2,3])
let rowNoVal : RowType = .noVal
row.associatedValue
rowNoVal.associatedValue

Remove enum from array of enums regardless of its argument

I have an enum BulletinOptions:
enum BulletinOption {
case notificationPermissions
case enableNotifications(eventId: String)
case join(hostName: String, eventId: String)
case share(type: SocialBulletinItem.BulletinType.Social, event: EventJSONModel, view: UIView)
case completedShare(type: SocialBulletinPageItem.SocialButtonType)
}
I have an array of these enums like this:
let array = [
.join(hostName: hostName, eventId: event.id),
.notificationPermissions,
.enableNotifications(eventId: event.id),
.share(type: .queue(position: 0, hostName: ""), event: event, view: view)
]
I want to create a function that can remove a specific enum from this array. I have this code:
func remove(
item: BulletinOption,
from options: [BulletinOption]) -> [BulletinOption] {
var options = options
if let index = options.firstIndex(where: {
if case item = $0 {
return true
}
return false
}) {
options.remove(at: index)
}
return options
}
What I want to do is this:
let options = remove(item: .enableNotifications, from: options)
However, this gives me two errors. The remove function says:
Expression pattern of type 'BulletinOption' cannot match values of type 'BulletinOption'
for the line:
if case item = $0
The second error is when calling that function:
Member 'enableNotifications' expects argument of type '(eventId: String)'
I just want to delete that enum regardless of its argument. How can I do that?
This is currently impossible.
What you are trying to do is essentially passing an enumeration case pattern as an argument to a method, so that that method can match each value in the array against that pattern. However, the swift guide says that:
An enumeration case pattern matches a case of an existing enumeration type. Enumeration case patterns appear in switch statement case labels and in the case conditions of if, while, guard, and for-in statements.
This means that enumeration case patterns are not allowed as arguments to functions. :(
So the best you can do is this:
array.filter {
if case .enableNotifications = $0 { return false } else { return true }
}

How to access implict closure parameters from higher closure level

I want to iterate an enum and then use $0 in a switch/case statement down one level in a closure that is called in a fetch operation inside the enum iteration loop, as follows:
enum GenericType: CaseIterable {
case purchase
case sale
// etc....
}
Then the code to use is as follows:
GenericType.allCases.forEach {
// let type = $0
Manager.fetchItems(ofType: $0, onSuccess: { (data) in
switch $0 {
case purchase:
// Do something
case sale:
// Do something
}
}
Xcode 10 assumes that $0 refers to data (the parameter in the closure) and gives this error message:
Anonymous closure arguments cannot be used inside a closure that has explicit arguments; did you mean 'data'?
I am able to make it work with before the fetch:
let type = $0
And then using type in the switch/case statement.
Is there a way to access the $0 shorthand argument from the higher level context inside a closure? Is the workaround a feasible solution?
Thx
$0 can only ever refer to the first closure context "up the chain". To access the parameters of outter closures, you need to name them:
GenericType.allCases.forEach { genericType in
Manager.fetchItems(ofType: genericType, onSuccess: { data in
switch genericType {
case .purchase: return
// Do something
case .sale: return
// Do something
}
})
}
Building on #Alexander and #Daniel's answer/inputs, this is the way I implemented the for loop without the switch-case statement.
enum GenericType: CaseIterable {
case purchase
case sale
var manager: GenericManager {
switch self {
case .purchase:
return PurchaseManager.shared
case .sale:
return SalesManager.shared
}
}
}
PurchaseManager and SaleManager are subclasses of GenericManager and they override the processFetchData(_:) method.
And the code is as follows:
GenericType.allCases.forEach { genericType in
FetchManager.fetchItems(ofType: genericType, onSuccess: { data in
genericType.manager.processFetchData(data)
})
}
Strictly speaking I only moved the switch statement from the biz logic to the enum declaration, but it makes it a bit more elegant.

Swift 2 value extraction from Enum

Prior to Swift 2, I would often use enums with associated values and add functions to extract specific values, like so:
public enum Maybe <T> {
case Unknown
case Known(T)
public var value: T? {
switch self {
case .Unknown: return nil
case .Known(let value): return value
}
}
}
This would allow me to do something like this:
let maybe = .Known("Value")
let val = maybe.value ?? "Another Value"
I would like to get rid of these convenience functions and rely on Swift 2's new syntax. This is possible doing something like:
let val: String
if case .Known(let value) = maybe {
val = value
} else {
val = "Another Value"
}
But I can't figure out how to condense this back into a single line using the ?? operator or even ternary operator.
Is this even possible or am I stuck with defining "extraction" optionals on the enum?
Update (clarification)
The Maybe enum is just an example, but the solution would need to work on Enums that have multiple associated values... like an Either:
public enum Either<L, R> {
case Left(Box<L>)
case Right(Box<R>)
public func left() -> L?{
switch self {
case let Left(value):
return value.value
default:
return nil
}
}
public func right() -> R?{
switch self {
case let Right(value):
return value.value
default:
return nil
}
}
}
The syntax I'm looking for would be something like:
let val = (case .Known(let value) = maybe) ?? "AnotherValue"
What I want to do is easily extract an associated value for a specific case, else provide a default.
For Either it might be something like:
let val = (case .Left(let value) = either) ?? "AnotherValue"
Make sense?
The syntax you want isn't possible in Swift today (and feels unlikely for Swift tomorrow, but I often am surprised). The best available solutions are extraction functions like left() -> L? and right() -> R?. case is not a generic value-returning function that you can extend. See Rob Rix's Either for some of the best current thinking on this problem.
A key choice in Swift is that there are many statements that are not expressions. switch is one of them. Until Swift makes things like switch and if be expressions, it will be very hard to build this kind of syntax IMO.
Just define ?? for it:
func ??<T>(lhs: Maybe<T>, #autoclosure defaultValue: () throws -> T) rethrows -> T {
switch lhs {
case .Unknown: return try defaultValue()
case .Known(let value): return value
}
}
let maybe = Maybe.Known("Value")
let val = maybe ?? "Another Value"
Doing it this way gives us some nice features in Swift2. For instance, we can lazily evaluate the rhs, and we can handle throwing in the rhs:
func computeIt() -> String {
print("LAZY!")
return "Computed"
}
maybe ?? computeIt() // Does not print "LAZY!"
Maybe.Unknown ?? computeIt() // Does print "LAZY!"
enum Error: ErrorType {
case Failure
}
func fail() throws -> String {
throw Error.Failure
}
try maybe ?? fail() // Doesn't throw
do {
try Maybe.Unknown ?? fail() // throws
} catch {
print("THROW")
}