2

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?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Heatherfield
  • 185
  • 4
  • 9
    This is the point of optionals. It forces you to deal with unknown values or invalid values. – rmaddy Dec 21 '17 at 21:17
  • 3
    How would you *"test whether the result has a value"* in C? C has no optionals, and all `int`s are valid. – Martin R Dec 21 '17 at 21:20
  • If the number strings comes from the user, then this solution is the only correct solution. If you have numbers that you internally keep as strings, then it's an architecture problem, you should keep them as numbers. – Sulthan Dec 21 '17 at 21:21
  • 1
    Actually it is much more hassle in C to test if a conversion from a string to an integer (e.g. with `strtol`) was successful or not. – Martin R Dec 21 '17 at 21:24
  • 1
    Indeed in C you can do what you suggest and add the two strings and then test the result but what if you miss out the test. Now you have a potential bug in the application that you may not spot until it's actually in use. The Swift approach maybe more verbose but it tries to prevent you from being able to get that bug into 'live' code as it's caught at the compile stage. – Upholder Of Truth Dec 21 '17 at 21:28
  • 3
    Your entire question falls on the premise that it's inefficient to do what you did. It's not, it's the minimal amount of instructions necessary to perform what you want safely. Each conditional binding is a branch that affirms there's some value, which is what you would be doing in C, anyway. – Alexander Dec 21 '17 at 21:31
  • Also you are comparing adding two strings as integers in C with the Swift equivalent being `let c = Int(a) + Int(b)` when in fact what the Swift more comparable to is the entire C functionality of adding them AND checking that they are valid. – Upholder Of Truth Dec 21 '17 at 21:39
  • It's only "inefficient" if you're doing things overly imperatively...just put it in a function that prepares the value needed and put the opening { on the same line as the if statement like a sane person – GetSwifty Dec 21 '17 at 22:30

5 Answers5

1

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
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
  • 3
    Note that Swift it is a type inferred language – Leo Dabus Dec 21 '17 at 21:43
  • @LeoDabus Yes. Should I remove the explicit typing from my answer? Does it matter? – Caleb Kleveter Dec 21 '17 at 21:56
  • 1
    The result will be the same. If it matters or not depends on your pov. Btw No need to declare a third object just `return c + d`. And about the error you should add an associated value and throw it otherwise just returning nil would be enough – Leo Dabus Dec 21 '17 at 21:57
  • Note that Swift already has the plus operator `+` for Strings. So you should choose another operator for your custom infix operator implementation otherwise you will probably get an error: ambiguous use of operator '+' – Leo Dabus Dec 21 '17 at 22:08
  • @LeoDabus I didn't get that error. That is why I had the explicit type setting before :). – Caleb Kleveter Dec 21 '17 at 22:11
  • So instead of forcing the type to be explicitly typed you should just change your operator – Leo Dabus Dec 21 '17 at 22:12
  • Is that really simpler (or clearer) than the original `if let c = Int(a), let d = Int(b) { let e = c + d }` ? – Martin R Dec 21 '17 at 22:15
  • @LeoDabus Yes Swift can *sometimes* infer the Type, but sometimes it can't, and sometimes you need to explicitly define the Type so the compiler knows which method to use. E.g. in this case, removing :Int will cause a compiler error because it doesn't know which + function to use – GetSwifty Dec 21 '17 at 22:16
  • @MartinR depends on how often you have to perform the function. – Caleb Kleveter Dec 21 '17 at 22:18
  • @PeejWeej yes but it is much better to use a different operator IMO – Leo Dabus Dec 21 '17 at 22:18
1

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.

Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
0

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

Paolo
  • 3,825
  • 4
  • 25
  • 41
0

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.

Stoyan
  • 1,265
  • 11
  • 20
0

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
}
Josh Homann
  • 15,933
  • 3
  • 30
  • 33