Swift3 Optionals Chaining in If Conditions Bug

Swift3 optionals chaining in IF conditions bug?

Optional comparison operators are removed from Swift 3.
SE-0121

You need to write something like this:

if test?.optionalInt ?? 0 > 4
{

}

Why can't Swift's greater-than or less-than operators compare optionals when the equality operators can?

It makes perfect sense for the equality operator to support optionals, because it's absolutely clear that for any integer valued variable i:

  • nil == nil
  • nil != i
  • i != nil
  • i == i if and only if their values are the same

On the other hand, it's not clear how comparison to nil should act:

Is i less than nil?

  • If I want to sort an array so that all the nils come out at the end, then I would want i to be less than nil.
  • But if I want to sort an array so that all the nils come out at the start, then I would want i to be greater than nil.

Since either of these are equally valid, it wouldn't make sense for the standard library to favor one over the other. It's left to the programmer to implement whichever comparison makes sense for their use-case.

Here's a toy implementation that generates a comparison operator to suite either case:

func nilComparator<T: Comparable>(nilIsLess: Bool) -> (T?, T?) -> Bool {
return {
switch ($0, $1) {
case (nil, nil): return false
case (nil, _?): return nilIsLess
case (_?, nil): return !nilIsLess
case let (a?, b?): return a < b
}
}
}

let input = (0...10).enumerated().map {
$0.offset.isMultiple(of: 2) ? Optional($0.element) : nil
}

func concisePrint<T>(_ optionals: [T?]) -> String {
return "[" + optionals.map { $0.map{ "\($0)?" } ?? "nil" }.joined(separator: ", ") + "]"
}

print("Input:", concisePrint(input))
print("nil is less:", concisePrint(input.sorted(by: nilComparator(nilIsLess: true))))
print("nil is more:", concisePrint(input.sorted(by: nilComparator(nilIsLess: false))))

Output:

Input: [0?, nil, 2?, nil, 4?, nil, 6?, nil, 8?, nil, 10?]

nil is less: [nil, nil, nil, nil, nil, 0?, 2?, 4?, 6?, 8?, 10?]

nil is more: [0?, 2?, 4?, 6?, 8?, 10?, nil, nil, nil, nil, nil]

optional chaining in Swift 3: why does one example work and not the other?

The problem in a nutshell ? the function for fractions reports a fault whereas the function for decimal numbers fails to detect bad input.

The function for decimal numbers does detect “bad” input. However, "700" does not contain ".", and you only call processDecimal(s:) if the string does contain ".". If the string doesn't contain "." and also doesn't contain "/", doubleFromDecimalOrFraction(s:) doesn't call any function to parse the string.

Using the Swift if let with logical AND operator &&

As of Swift 1.2, this is now possible. The Swift 1.2 and Xcode 6.3 beta release notes state:

More powerful optional unwrapping with if let — The if let construct
can now unwrap multiple optionals at once, as well as include
intervening boolean conditions. This lets you express conditional
control flow without unnecessary nesting.

With the statement above, the syntax would then be:

if let tabBarController = window!.rootViewController as? UITabBarController where tabBarController.viewControllers.count > 0 {
println("do stuff")
}

This uses the where clause.

Another example, this time casting AnyObject to Int, unwrapping the optional, and checking that the unwrapped optional meets the condition:

if let w = width as? Int where w < 500
{
println("success!")
}

For those now using Swift 3, "where" has been replaced by a comma. The equivalent would therefore be:

if let w = width as? Int, w < 500
{
println("success!")
}

Optional Chaining in one step?

Strictly spoken Example 2 is neither optional binding nor optional chaining because String(format...) returns a non-optional String and the format parameter must be non-optional, too.

Example 1 is the correct and recommended syntax to handle the optionals.

Edit: I totally agree with Alexander's answer (except that String(format:) returns String?)

Binary operator ' =' cannot be applied to operands of type 'String.IndexDistance?' (aka 'Optional Int ') and 'Int'

The reason is that Equatable works with optionals and Comparable does not. You have to unwrap the optional.

A suitable and safe solution is to optional bind the text property:

if let password = textFieldPassword.text, password.count >= 8 { ... }


Related Topics



Leave a reply



Submit