1

Optional chaining lets us make decisions on the existence of objects:

var text : String?
let len = text?.lengthOfBytes(using: .utf8) ?? 0

Which will always set len to an integer.

Is something similar possible for non-optional function arguments? Consider the following example, which already is less elegant with the ternary operator.

func length(text: String) -> Int {
    return text.lengthOfBytes(using: .utf8)
}

var text : String?
let len = (text != nil) ? length(text!) : 0

If we keep chaining, it easily gets a mess (and this is what I am actually looking at):

let y = (arg!= nil) ? (foo?.bar(arg!) ?? 0) : 0 // how to improve on this?

Besides, it also starts to get redundant, defining the default value twice.

Is there any more concise solution to the last line?

Eiko
  • 25,601
  • 15
  • 56
  • 71
  • 1
    I think optional chaining is enough, about your case I would say: `let len = length(text: text ?? "")` – mugx Jan 03 '18 at 11:19
  • Or `let len = text.map(length) ?? 0` – user28434'mstep Jan 03 '18 at 11:29
  • Using default parameters has downsides: 1) The function might be expensive to call, 2) It puts the logic for the default value into that function and away from its context. – Eiko Jan 03 '18 at 12:04
  • @Eiko just use the nil coalescing operator. Btw no need to create a method to get the bytes count of a string. Just get the count of the utf8 CharacterView `yourString.utf8.count` And if your string is optional `let len = text?.utf8.count ?? 0` – Leo Dabus Jan 03 '18 at 14:41
  • The bytes count was just an example, and no reason to improve the first example at all. The question is about how to get around the function call, and nil coalescing doesn't seem to provide a solution here. – Eiko Jan 03 '18 at 16:02

3 Answers3

1

The map method is the best approach for this. flatMap may also be of interest.

func length(text: String) -> Int {
    return text.lengthOfBytes(using: .utf8)
}

var text : String?
let len = text.map(length) ?? 0

If you have multiple arguments, you can pretty easily write a function (A?, B?) -> (A, B)?.

w8ite
  • 58
  • 5
0

Take into account that usage of the ?? will do impact on compilation time. In example that you describe, personally I like to use extensions that are clear to use.

extension Optional where Wrapped == String {
    func unwrappedLengthOfBytes(using: String.Encoding) -> Int {
        guard let text = self else {
            return 0
        }
        return text.lengthOfBytes(using: .utf8)
    }
}

Usage:

var text : String?
text.unwrappedLengthOfBytes(using: .utf8)
Oleg Gordiichuk
  • 15,240
  • 7
  • 60
  • 100
  • Thank you, interesting idea - not sure how this addresses the main issue (passing arguments that might be nil). The '??' coalescing works just fine. In fact, I'm trying to come up with more concise code instead of helper functions. I thought that Swift might have something onboard for that as well. – Eiko Jan 03 '18 at 13:24
  • 2
    This is very misleading readability wise. It makes It look like the object it is non optional. – Leo Dabus Jan 03 '18 at 14:44
0

You don't have to limit yourself to optional chaining. Swift provides other language constructs to express this clearly:

let y: Int
if let arg = arg, let foo = foo {
    y = foo.bar(arg)
} else {
    y = 0
}
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Yes, I know - I just got used to the coalescing shortcuts and wished that there was something similar for the arguments. – Eiko Jan 03 '18 at 19:28