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
nil
s come out at the end, then I would wanti
to be less thannil
. - But if I want to sort an array so that all the
nil
s come out at the start, then I would wanti
to be greater thannil
.
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. Optional
s 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
Overriding Methods in Swift Extensions
How to Dispatch_Sync, Dispatch_Async, Dispatch_After, etc in Swift 3, Swift 4, and Beyond
How to Set Associated Objects in Swift
Go to a New View Using Swiftui
Structure VS Class in Swift Language
Swift Protocol With "Where Self" Clause
Using a Dispatch_Once Singleton Model in Swift
Wait Until Swift For Loop With Asynchronous Network Requests Finishes Executing
Uitableview With More Than One Custom Cells With Swift
Firestore: How to Get Random Documents in a Collection
How to Provide a Localized Description With an Error Type in Swift
Creating Custom Tableview Cells in Swift
How to Get Ip Address in Swift