I'm trying without success to unwrap and cast multiple optionals via tuples. The strange thing is that unwrapping individual items works.
It has been suggested that I'm misusing the downcast as? operator,
and that's very likely, but I just can't see it.
Can anyone explain why this doesn't work?
class fooba { }
func unwrapPair(a: AnyObject?, _ b:AnyObject?) {
if let a = a as? fooba {
// true
}
if let b = b as? fooba {
// true
}
if let (a, b) = (a, b) as? (fooba, fooba) {
// false!?!?
}
}
unwrapPair(fooba(), fooba())
I don't think this is a duplicate of tuple "upcasting",
because aren't I downcasting? In any case, explaining exactly how
they're duplicates would be illuminating and an answer in itself.
Instead of separately trying to match each component with if-let combinations, you can just do the same in a more swift-y way:
switch (a, b) {
case (let a as String, _):
// Do whatever you need to do with a
return true
case (_, let b as String):
// Do whatever you need to do with b
return true
case (let a as NSNumber, let b as NSNumber):
// Do whatever you need to do with a and b
return false
default:
return true
}
Note also that the type of downcast you are trying to do is currently impossible - look at this swift bug: tuple 'as?' downcast broken
Related
I'm coming from a C++ background, but I'm learning Swift 4 for MetalKit. Since I'm only used to C++, the whole focus on "optionals" comes a little foreign to me. While reading along with the Swift 4 book published by Apple, I came along the following problem:
Say I have two "String"s a and b:
let a = "12"
let b = "24"
I want to add these two together. It is bad practise AND illegal to write
let c = Int(a) + Int(b)
Because a and b are both Strings, their typecast into Int is an optional: the conversion may have failed. The solution seems to be
if let c = Int(a), let d = Int(b)
{
let e = c + d
}
But this is a bit of a hassle: I'm copying way more than I would in a C program, where I could simply add a and b and then test whether the result has a value. Is there a more efficient, better way to perform this addition?
As #rmaddy said in his comment, this is the whole point of optionals. It is supposed to make you work at it, resulting in safer code.
If you are willing to go to a bit of up-front work, you can create a custom operator that will throw an error if the result is converting to an Int is nil:
enum Err: Error {
case nilValue
}
func +(lhs: String, rhs: String)throws -> Int {
guard let c = Int(lhs), let d = Int(rhs) else {
throw Err.nilValue
}
return c + d
}
(You might have to add infix operator + to this snippet.)
You can then use it like this:
do {
let i: Int = try a + b
print(i)
} catch {
// Catch error here
}
Or use try?. This will return nil if an error is thrown:
let i: Int? = try? a + b
If you don't want to use the type annotations, you can give the operator a different name, i.e.:
infix operator +?: AdditionPrecedence
func +?(lhs: String, rhs: String)throws -> Int
If you're looking for a way to write this in one line, you can take advantage of the map and flatMap variants for optionals:
let a = "12"
let b = "24"
let c = Int(a).flatMap { aʹ in Int(b).map { bʹ in aʹ + bʹ }}
c is of type Optional<Int> and will be nil when either Int(a) or Int(b) fails. The outer map operation must be a flatMap to get the correct result type. If you replace flatMap with map, the type of c would be Optional<Optional<Int>>, or Int??.
Whether you consider this readable is at least partly a matter of familiarity with the concept of mapping over optionals. In my experience, most Swift developers prefer unwrapping with if let, even if that results in more than one line.
Another alternative: wrap this pattern of unwrapping two optionals and applying a function to the unwrapped values in a generic function:
func unwrapAndApply<A, B, Result>(_ a: A?, _ b: B?, _ f: (A, B) -> Result) -> Result? {
guard let a = a, let b = b else { return nil }
return f(a, b)
}
This function works on all inputs, regardless of the underlying types. Now you can write this to perform the addition:
let d = unwrapAndApply(Int(a), Int(b), +)
The unwrapAndApply function only works on two input arguments. If you need the same functionality for three, four, five, … inputs, you’ll have to write additional overloads of unwrapAndApply that take the corresponding number of arguments.
You have many ways in which you can do this. I will show you one that it is appropriate if you have to do this often:
extension Int {
static func addStrings(_ firstString: String, _ secondString: String) -> Int? {
guard let firstNumber = Int(firstString) else { return nil }
guard let secondNumber = Int(secondString) else { return nil }
return firstNumber + secondNumber
}
}
you should use it like this:
let number = Int.addStrings("1", "2")
I am using one awesome feature of Swift - extensions. With them you can add methods and computed variables to every class you want. Optionals and extensions are very important things when it comes to Swift development. You should read Apple docs carefully.
You can define your own operator to add two optionals together:
let a = "12"
let b = "24"
infix operator ?+: AdditionPrecedence
func ?+ (left: Int?, right: Int?) -> Int? {
guard let left = left,
let right = right else {
return nil
}
return left + right
}
let c = Int(a) ?+ Int(b)
The result is an optional. If you don't want the result to be optional you need to provide a suitable default value. For instance if you think 0 is appropriate:
infix operator ?+: AdditionPrecedence
func ?+ (left: Int?, right: Int?) -> Int {
guard let left = left,
let right = right else {
return 0
}
return left + right
}
There are a couple of ways to handle optionals besides the if let method you showed.
One is to place a guard let statement at the beginning of the block of code in which you are using the optionals. This allows you to avoid having tons of nested if let statements:
guard let c = Int(a), let d = Int(b) else { return }
// use `c` and `d` as you please
// ...
You can also use the nil coalescing operator to define a default value (e.g. 0):
let c = (Int(a) ?? 0) + (Int(b) ?? 0)
In this situation, if either Int(a) or Int(b) fails, they will be replaced with 0, respectively. Now c is an Int instead of an Int? and can be used freely without unwrapping. This may or may not be appropriate depending on what can happen if you use a default value rather than the intended one.
Further reading: What is an optional value in Swift?
Alternatively, you can create a custom operator to allow you to numerically "add" two strings:
infix operator +++: AdditionPrecedence
func +++(_ a: String, _ b: String) -> Int? {
if let intA = Int(a), let intB = Int(b) {
return intA + intB
} else {
return nil
}
}
// use it like so:
let c = "12" +++ "24"
// now c is Int? and you can check if the result is optional
if let d = c {
}
More about custom operators in the Swift Documentation: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html
This question already has answers here:
Is it possible to cast Any to an Optional?
(5 answers)
Closed 5 years ago.
Running the below piece of code on Xcode 9 playground, I noticed that nil does not equal nil. How can we check if b is in fact nil at line #4?
let s: String? = nil
var a: Optional<Any> = .some(s as Any)
if let b = a {
if b == nil {
print("b equals nil")
} else {
print("b doesn't equal nil. b is \(b)")
}
}
UPDATE 1:
I do understand why the behavior is so. What I am looking for is how to check if b is nil since comparing it with nil doesn't work here.
UPDATE 2:
To avoid confusion, I changed the var name to b at line 3 (if let b = a)
UPDATE 3:
The answer turns out to be like this:
let s: String? = nil
var a: Optional<Any> = .some(s as Any)
if let b = a {
if Mirror(reflecting: b).descendant("Some") as? String == nil {
print("b equals nil")
} else {
print("b doesn't equal nil. b is \(b)")
}
}
When you write:
let s: String? = nil
You basically create a generic enum value: s: Optional<String> = .none
enum Optional<T> {
case .some(T)
case .none
}
Then you wrap that value into a new enum as associated value for .some :
var a: Optional<Any> = .some(s as Any)
which is the same as typing:
var a: Optional<Any> = .some(Optional<String>.none as Any)
so a by itself is not nil. It contains .some value wrapped.
When you unwrap it, you still get nested wrapped Optional. This is why you pass further than line #4. But inherent value of Optional is still nil. This is why you see it printed.
As the warning states:
Comparing non-optional of type Any to nil always returns false. Here the if let statement make 'a' variable a non-optional type and hence you always get false.
As for answering the original question, you have done it the right way thats how you can check for a nil value:
if a == nil {}
or like this
if (!a) {}
For more on how swift represents optional values check the answer in this question-
Why non optional Any can hold nil?
I don't know why you are creating another variable a.
But I tried your code like this and it worked:
let s: String? = nil
let a: Any? = s
//if let a = a {
if a == nil {
print("a equals nil")
} else {
print("a doesn't equal nil. a is \(String(describing: a))")
}
//}
Nullable types present in swift as (<YourTypy>, nil). For example, String? presents as (String, nil). So, a type of myVar: String? can be equal String or nil.
When you use if let a = a structure, it moves into true branch only if type of a equals YourType, not nil. And in true branch you will have variable a with type YourType, not YourType?. So, a in true branch will never equal nil.
You should write notification about nil in false branch of if let
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
}
Is it possible to create a Generic enum function that returns a boolean if an enum instance is an enum Type or does this already exist and I don't know about it?
I use a ton of enums in my projects. Very often I define enums with associated values.
Simple Example:
enum Mode {
case new
case edit(Record) // Record is a struct type
}
I regularly check whether an enum instance is a specific enum case. Many times, however, I don't need to check the associated value. I'm looking for a convenient way to check the case. Each method I know about has a downside.
Method 1-2: If Case pattern matching or Switch case
let myMode = Mode.edit
if case Mode.edit(_) = myMode {
// do something
}
switch mode {
case .edit:
// do something
default:
break
}
Downsides:
Cannot assign the check to a variable directly. Must do so in closure
cannot use this pattern directly as an argument to a function
Method 3-5 Implement an enum function or check value of computed property or Equatable protocol
Downsides:
must be implemented for each enum type
Instead, I'm looking for a way to write a Generic function that returns a boolean if a enum instance matches an enum case. Something that can be written once and be applied to all enums. Similar to Generic function for Struct and Class Types:
func checkType<T, S> (a: T, _: S.Type) -> Bool {
return a is S // though you could just call this directly
}
I don't think there is a good idiomatic way of achieving this. The only thing that comes to mind is to compare the raw memory of your enum instance against a dummy instance with the desired case.
As we don't care about associated values we would only need to require their respective last byte to be identical.
func unsafeEqualityLastByteOnly<A>(_ lhs: A, _ rhs: A) -> Bool {
var (lhs, rhs) = (lhs, rhs)
let offset = MemoryLayout<A>.size - 1
return withUnsafePointer(to: &lhs) { lhsPtr in
withUnsafePointer(to: &rhs) { rhsPtr in
let lhsPtr = unsafeBitCast(lhsPtr, to: UnsafeRawPointer.self)
let rhsPtr = unsafeBitCast(rhsPtr, to: UnsafeRawPointer.self)
return memcmp(lhsPtr.advanced(by: offset), rhsPtr.advanced(by: offset), 1) == 0
}
}
}
This is far from pretty, but it works.
enum Test {
case a(Int)
case b(Int)
}
let a1 = Test.a(1)
let a2 = Test.a(2)
let b1 = Test.b(1)
let b2 = Test.b(2)
unsafeEqualityLastByteOnly(a1, a1) // true
unsafeEqualityLastByteOnly(a1, a2) // true
unsafeEqualityLastByteOnly(a2, a2) // true
unsafeEqualityLastByteOnly(b1, b1) // true
unsafeEqualityLastByteOnly(b1, b2) // true
unsafeEqualityLastByteOnly(b2, b2) // true
unsafeEqualityLastByteOnly(a1, b1) // false
unsafeEqualityLastByteOnly(a1, b2) // false
unsafeEqualityLastByteOnly(a2, b1) // false
unsafeEqualityLastByteOnly(a2, b2) // false
Use your own judgement to decide whether or not this is something you want in your project. It's obviously not a technique that should be recommended without any reservations.
I have a Swift optional type and I want to chain a transformation to it (map) and eventually pass it to a closure that prints it.
I want avoid unwrapping (using !) the optional type because I don't want to execute the closure unless we have something inside the optional.
In Scala we use foreach as a map that returns Unit.
val oi = Option(2)
oi.map(_+1).foreach(println)
In Swift I get only until the increment (and the conversion to string)
let oi:Int? = 1
let oiplus = oi.map({$0 + 1}).map({String($0)})
// now we have "2"
Now how do I give this to print() only in the case the optional is not nil?
I would just map over it
let oi: Int? = 1
let oiplus = oi.map{ $0 + 1 }.map { String($0) }
oiplus.map { print($0) }
Arguably not nice to map for producing a side-effect, but there's no foreach for Optional in Swift.
This also produces an unused result warning in Swift 2.
If the semantic (and the warning) bug you, you can always define your own foreach for Optional. It's pretty straightforward:
extension Optional {
func forEach(f: Wrapped -> Void) {
switch self {
case .None: ()
case let .Some(w): f(w)
}
}
}
and then
let oi: Int? = 1
let oiplus = oi.map{ $0 + 1 }.map { String($0) }
oiplus.forEach { print($0) }
which resembles scala as much as possible.
While I usually do not recommend using map for side-effects, in this case I believe it's the cleanest Swift.
_ = oiplus.map{ print($0) }
There is now a SequenceType.forEach method that is explicitly for this problem, but Optional is not a SequenceType. You could of course extend Optional to add a forEach-like method (I'd probably call it apply in Swift).
It's worth noting that this just avoids the if-lit, which is barely any longer:
if let o = oiplus { print(o) }
But it admittedly doesn't chain as nicely as:
_ = oi
.map { $0 + 1 }
.map { print($0) }
See also http://www.openradar.me/23247992, which is basically Gabriele's answer.