2

Say I have the following scenario:

var str: String
var num: Int?

if let num = num {
    str = "\(num) foo"
}
else {
    str == "? foo"
}

can the flow control statements be simplified to one line? I.e. something along the lines of:

var str: String
var num: Int?

str = "\(String(num) ?? "?") foo"
Archie Gertsman
  • 1,601
  • 2
  • 17
  • 45

4 Answers4

4

You can use call the description property with optional chaining and then use the nil coalescing operator ?? to either unwrap that or replace it with "?" if num is nil (causing the optional chain to return nil):

str = "\(num?.description ?? "?") foo"

Example:

for num in [nil, 5] {
    let str = "\(num?.description ?? "?") foo"
    print(str)
}
? foo
5 foo
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • I'm pretty sure I've read somewhere in the language docs an advice to avoid explicitly calling the `description` property (rather favouring the `String(describing:)` initializer), but I can't find such an explicit advice when I'm looking around now. Anyway, a clever trick to be able to fall back to the `nil` coalescing operator! :) Btw, both this and the accepted answer favour string interpolation over concatenation; is there some best practice regarding this, or is this just a matter of taste? (Concatenation might come with an overhead, but imho with somewhat better readability; maybe taste). – dfrib Jul 23 '17 at 13:53
  • If you use multiple string concatentations in the same row, Swift freaks out, so I tend to avoid it. – vacawama Jul 23 '17 at 13:55
  • That's good to know, I haven't run into it myself (except for the buggy case of concatenation of implicitly unwrapped optionals); thanks! – dfrib Jul 23 '17 at 14:10
  • @dfri, due to Swift type inference, multiple String concatenations slow down Swift compile times and lead to error messages about line is too complex to be completed in reasonable time. For this reason, I tend to avoid them, even though one is certainly OK. – vacawama Jul 23 '17 at 14:10
  • 1
    @dfri, I remember some advice about not calling `description` as well, but it may have been here on SO. I searched for official documentation and couldn't find it before posting this answer. I figured putting the answer up on SO was a surefire way of someone pointing this out if it is truly a thing. – vacawama Jul 23 '17 at 14:16
2

Here's one solution:

let str = "\(num.map { String($0) } ?? "?") foo"

This returns "? foo" if num is nil or it returns "42 foo" if num is set to 42.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • `Optional`:s `map(_:)` already seen in action! :) btw, for brevity/readability, possibly string concatenation could be preferred in this case over interpolation (even if it possible comes with a negligible overhead); e.g. `let str = (num.map(String.init) ?? "?") + " foo"`. – dfrib Jul 23 '17 at 11:27
  • @dfri What do you mean "already seen in action"? I see the smiley but I don't get what you mean. – rmaddy Jul 23 '17 at 15:51
  • I was referring to [this comment of yours to an answer of mine](https://stackoverflow.com/questions/45148009/convert-string-values-from-server-formatted-as-double-to-int/45148712#comment77268458_45148712) a few days ago. – dfrib Jul 23 '17 at 16:53
1

Not exactly in a clean one liner.

But if you write a simple extension on Optionals, like so:

extension Optional where Wrapped: CustomStringConvertible {
    var nilDescription: String {
        switch self {
        case .none: return "?"
        case let .some(wrapped): return wrapped.description
        }
    }
}

you could write

let str = "\(num.nilDescription) foo"

I think this approach would be more readable.

Benzi
  • 2,439
  • 18
  • 25
0

You can also write it like below.

var number:Int? = 0
var str = String(format: "%@ foo", (number != nil) ? NSNumber(integer: number!) : "?");

Hope it will help you.

Malav Soni
  • 2,739
  • 1
  • 23
  • 52