Why Can't Swift'S Greater-Than or Less-Than Operators Compare Optionals When the Equality Operators Can

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]

Sorting Struct - Binary operator '' cannot be applied to two 'String?' operands

You cannot compare optional strings, as written in the error message. Unwrap the strings and then try to compare.

self.hosts.sorted { lhs, rhs in
guard let lhsName = lhs.hostName, let rhsName = rhs.hostName else { return false }
return lhsName < rhsName
}

EDIT - The above solution is incorrect as it breaks the transitivity of the sort() function and will not work with large sets of data; Thanks to @Alexander - Reinstate Monica for pointing out.

A proper solution would be to either force-unwrap the values which is not recommended or provide a nil-coalescing value like so:

let sortedArray = self.hosts.sorted { lhs, rhs in
lhs.hostName ?? "" < rhs.hostName ?? ""
}

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
{

}

Strange generic function appear in view controller after converting to swift 3

That is interesting. Before the latest Swift 3, you could
compare optional values, for example

let a: Int? = nil
let b: Int? = 4

print(a < b) // true

and nil was considered less than all non-optional values.

This feature has been removed (SE-0121 – Remove Optional Comparison Operators) and the above code would fail to compile
in Xcode 8 beta 6 with


error: value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?

Apparently, the Swift migrator solves that problem for you by
providing a custom < operator which takes two optional operands
and therefore "restores" the old behavior.

If you remove that definition then you should see where the
comparison is done in your code. Then try to update your code
and remove the optional comparisons.

Comparing two Doubles in Swift: Type of expression is ambiguous without more context

The function

Double.init(_ string: String) returns an Optional (Type Double?).

It would be like you wrote this code:

var a: Double? = nil
var b: Double? = 7

if a > b {
print("A is greater")
}

Is nil greater than or less than 7? It's undefined. Optionals are not Comparable.

You need to decide what to do if either or both strings can't be converted to Double:

guard let operand = Double(realOperand),
value = Double(realValue) else {
// Crash on purpose. Not a great option for user-entered Strings!
fatalError("Could not convert strings to Doubles")
}

return operand > value

Most elegant way to compare two optionals in Swift

You can use !==

From The Swift Programming Language

Swift also provides two identity operators (=== and !==), which you use to test wether two objects references both refer to the same object instance.

Some good examples and explanations are also at Difference between == and ===

On @PEEJWEEJ point, doing the following will result in false

var newValue: AnyObject? = "String"
var oldValue: AnyObject? = "String"

if newValue === oldValue {
print("true")
} else {
print("false")
}

Swift error comparing two arrays of optionals

The issue here is the distinction between something having an == operator, versus something being “equatable”.

Both Optional and Array have an == operator, that works when what they contain is equatable:

// if T is equatable, you can compare each entry for equality
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
// if T is equatable, you can compare the contents, if any, for equality
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool

let i: Int? = 1
let j: Int = 1
i == j // fine, Int is Equatable
["a","b"] == ["a","b"] // and so is String

But they themselves do not conform to Equatable. This makes sense given you can put a non-equatable type inside them. But the upshot of this is, if an array contains a non-equatable type, then == won’t work. And since optionals aren’t Equatable, this is the case when you put an optional in an array.

You'd get the same thing if you tried to compare an array of arrays:

let a = [[1,2]]
let b = [[1,2]]
a == b // error: `==` can’t be applied to `[Array<Int>]`

If you wanted to special case it, you could write == for arrays of optionals as:

func ==<T: Equatable>(lhs: [T?], rhs: [T?]) -> Bool {
if lhs.count != rhs.count { return false }
for (l,r) in zip(lhs,rhs) {
if l != r { return false }
}
return true
}

For a counter-example, since Set requires its contents to be hashable (and thus equatable), it can be equatable:

let setarray: [Set<Int>] = [[1,2,3],[4,5,6]]
setarray == [[1,2,3],[4,5,6]] // true


Related Topics



Leave a reply



Submit