Reputation: 185
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?
Upvotes: 2
Views: 1165
Reputation: 135550
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.
Upvotes: 1
Reputation: 11494
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
Upvotes: 1
Reputation: 3935
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
Upvotes: 0
Reputation: 16327
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
}
Upvotes: 0
Reputation: 1305
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.
Upvotes: 0