Pattern match and conditionally bind in a single Switch statement - swift

Is there a way to write this if/else if/else ladder as a switch statement?
let x: Any = "123"
if let s = x as? String {
useString(s)
}
else if let i = x as? Int {
useInt(i)
}
else if let b = x as? Bool {
useBool(b)
}
else {
fatalError()
}
Here's my attempt:
switch x {
case let s where s is String: useString(s)
case let i where i is Int: useInt(i)
case let b where b is Bool: useBool(b)
default: fatalError()
}
It successfully chooses the right path, but s/i/b are still of type Any. The is check doesn't have any effect in casting them. This forces me to force cast with as! before usage.
Is there a way to switch on the type, and bind it to a name, all in one switch statement?

Sure, you can use the conditional casting pattern case let x as Type:
let x: Any = "123"
switch x {
case let s as String:
print(s) //use s
case let i as Int:
print(i) //use i
case let b as Bool:
print(b) //use b
default:
fatalError()
}

Related

Match optional in switch statement

Given a non-optional value, how can I match against an optional in a switch statement?
For example:
let test = "some string"
let x: String? = nil
let y: String? = "some string"
let z: String? = "another string"
switch test {
case x:
print(x)
case y:
print(y)
case z:
print(z)
default: break
}
results in:
Expression pattern of type 'String?' cannot match values of type 'String'
for each case...
I've read the swift docs on patterns and switch but I can't seem to find a way of making this work.
I know I can work around this, but there must be a way of making this work...
Edit
As requested, here is my actual use case. Note all text fields are optional...
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// TODO: Replace with switch if possible
// switch textField {
// case nameTextField:
// usernameTextField?.becomeFirstResponder()
// case usernameTextField:
// dateOfBirthTextField?.becomeFirstResponder()
// case dateOfBirthTextField:
// phoneTextField?.becomeFirstResponder()
// case phoneTextField:
// phoneTextField?.resignFirstResponder()
// default: break
// }
if textField == nameTextField {
usernameTextField?.becomeFirstResponder()
} else if textField == usernameTextField {
dateOfBirthTextField?.becomeFirstResponder()
} else if textField == dateOfBirthTextField {
phoneTextField?.becomeFirstResponder()
} else if textField == phoneTextField {
phoneTextField?.resignFirstResponder()
}
return false
}
Swift 4.0
You can check with optional case as like below.
Hope this will help you.
let test = "some string"
let x: String? = nil
let y: String? = "some string"
let z: String? = "another string"
switch test {
case let val where val == x:
print(x)
case let val where val == y:
print(y)
case let val where val == z:
print(z)
default:
break
}
For your actual use-case: I would create an array of all non-nil
text fields. Then you can lookup the current text field in that
array and make the next one in the list the first responder:
let fields = [nameTextField, usernameTextField, dateOfBirthTextField, phoneTextField]
.flatMap { $0 }
if let idx = fields.index(of: textField), idx + 1 < fields.count {
fields[idx + 1].becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
This is less error-prone and can easily be modified if the number or
order of the text fields changes.

A single expression for guard case let with type casting of the associated type?

I have the following enum:
enum JSONData {
case dict([String:Any])
case array([Any])
}
I would like to do a pattern matching assignment with type casting using guard case let. I not only want to make sure that someData is .array, but that its associated type is [Int] and not just [Any]. Is this possible with a single expression? Something like the following:
let someData: JSONData = someJSONData()
guard case let .array(anArray as [Int]) = someData
else { return }
But the above does not compile; the error is downcast pattern value of type '[Int]' cannot be used. I know this is possible with the following but I'd rather do it in a single expression if possible.
guard case let .array(_anArray) = someData, let anArray = _anArray as? [Int]
else { return }
Edit on April 27, 2018: An update to this situation: you may now use the following syntax as long as the cast type is not a Collection:
enum JSONData {
case dict([String:Any])
case array(Any) // Any, not [Any]
}
func f() {
let intArray: JSONData = .array(1)
guard case .array(let a as Int) = intArray else {
return
}
print("a is \(a)")
}
f() // "a is 1"
If you attempt to use a collection type such as [Int], you receive the error error: collection downcast in cast pattern is not implemented; use an explicit downcast to '[Int]' instead.
What you're trying to do is not possible due to a limitation in Swift's pattern matching implementation. It's actually called out here:
// FIXME: We don't currently allow subpatterns for "isa" patterns that
// require interesting conditional downcasts.
This answer attempts to explain why, though falls short. However, they do point to an interesting piece of information that makes the things work if you don't use a generic as the associated value.
The following code achieves the one-liner, but loses some other functionality:
enum JSONData {
case dict(Any)
case array(Any)
}
let someData: JSONData = JSONData.array([1])
func test() {
guard case .array(let anArray as [Int]) = someData else {
return
}
print(anArray)
}
Alternatively, the same one liner can be achieved through a utility function in the enum definition that casts the underlying value to Any. This route preserves the nice relationship between the cases and the types of their associated values.
enum JSONData {
case dict([String : Any])
case array(Array<Any>)
func value() -> Any {
switch self {
case .dict(let x):
return x
case .array(let x):
return x
}
}
}
// This coercion only works if the case is .array with a type of Int
guard let array = someData.value() as? [Int] else {
return false
}

How to pattern match enum in a single line in Swift?

In Swift, if I have an enum:
enum MyEnum {
case foo(FooType)
case bar(BarType)
}
I can pattern match with either switch
switch enumValue {
case .foo(let fooValue):
// ... use fooValue
case .bar(let barValue):
// ...
}
...or case let
if case let .foo(fooValue) = enumValue {
// ... use fooValue
}
QUESTION: is it possible to match in one expression to check if it's of type .foo(FooType) to produce an optional FooType??
The equivalent multiline version is:
var x: FooType?
if case let .foo(fooValue) = enumValue {
x = fooValue
}
Something to the effect of
let .foo(x) = enumValue or nil
where fooValue is bound to a FooType? value or nil if it's not .foo.

Given a Swift `Any` type can I determine if it's an `Optional`?

Given a value of type Any is it possible to check and see if it's an Optional or not?
This code doesn't work because instead of checking to see if it's optional or not it's trying to cast it, and it passes
let a: Any = "5"
switch a {
case let optional as Optional<Any>:
if case .some(let value) = optional {
print("wrapped value of `\(a)` is `\(value)`")
}
default:
print("\(a) is not an optional")
}
Base on #dfri's solution
private func isOptional(input: Any) -> Bool {
let mirror = Mirror(reflecting: input)
let style = mirror.displayStyle
switch style {
case .some(.optional):
return true
default:
return false
}
}
You can use runtime introspection using Mirror:
let foo: String? = "foo"
let bar: String = "bar"
var a: Any = foo
// if wrapping an optional, the reflection of the value has
// a displaystyle "optional"
if let displayStyle = Mirror.init(reflecting: a).displayStyle {
print(displayStyle) // optional
}
// for a non-optional fundamental native type: no displaystyle
a = bar
if let displayStyle = Mirror.init(reflecting: a).displayStyle {
print(displayStyle)
} // prints nothing
Optional/non-optional example where the underlying type is user-defined (non native):
struct Foo {}
let foo: Foo? = Foo()
let bar: Foo = Foo()
var a: Any = foo
// if wrapping an optional, the reflection of the value has
// a displaystyle "optional"
if let displayStyle = Mirror(reflecting: a).displayStyle {
print(displayStyle) // optional
}
// for a non-optional non-fundamental type:
a = bar
if let displayStyle = Mirror(reflecting: a).displayStyle {
print(displayStyle) // struct
}
If you don't want need to use the binded displayStyle variable (e.g. for printing) but simply want check whether the wrapped value is any kind of optional, you can add a boolean clause to the if statement that holds the optional binding of the displayStyle case,
if let displayStyle = Mirror(reflecting: a).displayStyle,
displayStyle == .optional {
// is an optional ...
}
... or remove the binding entirely in favour of a single conditional expression using the nil coalescing operator (??)
if Mirror(reflecting: a).displayStyle ?? .class == .optional {
// is an optional
}
Note however that for all the methods above, this simply tells you as dev whether the type wrapped by the Any instance is optional or not: Swifts typing system still knows nothing of the sort.
let a: Any = "5"
let b: Any? = "5"
if type(of: a) == Optional<Any>.self {
print("a is optional")
} else {
print("a is not optional")
}
if type(of: b) == Optional<Any>.self {
print("b is optional")
} else {
print("b is not optional")
}
/*
a is not optional
b is optional
*/
another example ...
let a: Any = 5
let b: Any? = 5
let c: Any = "5"
let d: Any? = "5"
let arr: [Any] = [a,b as Any,c,d as Any]
arr.forEach { (x) in
print(type(of: x))
}
/*
Int
Optional<Any>
String
Optional<Any>
*/

Satisfy one of multiple constraints in an if let construct

I want to satisfy multiple constraints in an if let construct. I know we can use a "," (comma) to unwrap multiple values but they both have to be successfully unwrapped.
For example :
var str: String? = "Hello"
var x: Int? = 10
if let intValue = x, stringValue = str {
// do something here.
} else {
}
I want if one of the conditions is successfully unwrapped, then a block will execute.
for example:
class CustomClass {
var x = 10
static func someValue() -> String? {
return "some"
}
}
var flag: Bool? = false
var x: Int? = 10
var status: String
in this i want if either customclass someValue function or x value any of successfully unwrapped and flag is true then code executes
You can create a tuple and use a switch like so:
switch (str, x) {
case (.Some,.Some):
print("Both have values")
case (.Some, nil):
print("String has a value")
case (nil, .Some):
print("Int has a value")
case (nil, nil):
print("Neither has a value")
}