Swift optionals and equality operator - swift

Looking for a doc reference or a name or link on this particular behavior, which is similar to optional binding but isn't talked about in that part of the docs.
I can test an optional with the == operator, and test against both nil and its actual value, without doing any explicit unwrapping:
var toggle: Bool? = nil
if (toggle == true || toggle == nil) {
// do something
}
This compiles and works as you'd want it to, but what's happened here is that I haven't had to unwrap toggle! explicitly; the == has safely done it for me.
It's convenient but I confess to being a little surprised when I noticed it. Is this just a behavior of the default == implementation? Or is something else in the language happening here? Thanks for insight.

Swift has an equality operator taking two optionals values
(of an Equatable base type):
public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
The implementation can be found at Optional.swift:
public func == <T: Equatable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
and it does what one would expect: The operands are equal if they
are both nil, or if they are both not nil and the unwrapped
values are equal.
Similar comparison operators < etc taking optionals have been
removed in Swift 3, compare
SE-0121 Remove Optional Comparison Operators:
Remove the versions of <, <=, >, and >= which accept optional operands.
Variants of == and != which accept optional operands are still useful, and their results unsurprising, so they will remain.
So this works as expected:
let b: Bool? = nil
print(b == true) // prints "false"
But as matt pointed out, this can not be done with implicitly unwrapped
optionals, here the left operand will be unwrapped:
let b: Bool! = nil
print(b == true) // crashes

Related

why multiple optionals(wrap nil) compare with operator "==" return false?

var opt1: Int??? = nil
var opt2: Int?? = nil
print(opt1 == opt2) // why is false?
At first I thought it was because of the different types(Int??? and Int??), so I custom a operator <==> and use the same code as the Optional' == source code to implement it:
extension Optional {
public static func <==> (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l <==> r
case (nil, nil): //I think it should match here
return true
default:
return false
}
}
}
print(opt1 <==> opt2) // false
(the source code link: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift)
I saw from the breakpoint that lhs and rhs become the same type.
In other words, this is not caused by the type.
And Optional' ~= operator implementation in the source code:
public static func ~=(lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool {
switch rhs {
case .some:
return false
case .none:
return true
}
}
According to the code above,I think case (nil, nil): in static func <==> should match, so print(opt1 == opt2) and print(opt1 <==> opt2) should be true, but why it's false?
There are four kinds of values that a Int??? can have:
.none (nil)
.some(.none) (a non nil Int??? wrapping a nil Int??)
.some(.some(.none)) (a non nil Int??? wrapping a non nil Int?? wrapping a nil Int?)
.some(.some(.some(n))) where n is an Int (an Int wrapped by 3 layers of Optionals)
Similarly, there are three kinds of values that an Int?? can have
.none
.some(.none)
.some(.some(n)) where n is an Int
When you write nil, it always means .none of whatever type that context needs, so both opt1 and opt2 are .none here.
What happens when you pass them to the == operator? Well, After some overload resolution/type inference, the compiler finds that == takes a two Int??? parameters, but you have passed an Int?? as the second argument. Luckily, there exists a conversion from any value t of type T to type T? - .some(t).
So after being passed into the ==, opt2 changes from .none to .some(.none), as you can see from this code snippet:
func test(lhs: Int???, rhs: Int???) {
if case .none = lhs, case .some(.none) = rhs {
print("lhs is .none and rhs is .some(.none)")
}
}
test(lhs: opt1, rhs: opt2) // prints
Hence they are "not equal".
The debugger seems to be showing both .none and .some(.none) as "nil", possibly because both of their debugDescription/description is "nil".
If you don't care about the multiple layers of optionals, you can just unwrap them to a single layer by doing as? Int, then compare:
print((opt1 as? Int) == (opt2 as? Int)) // true

Custom infix operators and optionals

class TreeNode: Equatable {
static func ==(lhs: TreeNode, rhs: TreeNode) -> Bool {
lhs.val == rhs.val && lhs.left == rhs.right && lhs.right == rhs.left
}
var val: Int = 0
var left, right: TreeNode?
}
This code compiles and even works. But why? left and right variables are optional, isn't I supposed to unwrap it first in the body of static func ==?
Actually it isn't quite an equation. As you can see it's rather some sort of symmetrical equation. So I would like to define custom operator with different name for this purpose:
infix operator =|=: ComparisonPrecedence
class TreeNode {
static func =|=(lhs: TreeNode, rhs: TreeNode) -> Bool {
lhs.val == rhs.val && lhs.left =|= rhs.right && lhs.right =|= rhs.left
}
var val: Int = 0
var left, right: TreeNode?
}
And now it doesn't compile due to the reason I've mentioned earlier. It wants me to unwrap the optionals first.
Actually it would be great if it "just works" like in the case of "=="))) Because not having to unwrap the optionals explicitly would be convenient here.
So I want to understand why it behaves differently in these two situations.
This code compiles and even works. But why?
It is simply because there is an == operator declared for all Optional<Wrapped> where Wrapped is Equatable, like this:
static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool
TreeNode is Equatable in your first code snippet, so it works.
In your second code snippet, you haven't declared a =|= operator that operates on two TreeNode?. You can do that by either putting this in global scope...
func =|= (lhs: TreeNode?, rhs: TreeNode?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): // both nil
return true
case (let x?, let y?): // both non-nil
return x =|= y // compare two non-optional tree nodes
default:
return false
}
}
or writing an Optional extension:
extension Optional where Wrapped == TreeNode {
static func =|= (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): // both nil
return true
case (let x?, let y?): // both non-nil
return x =|= y // compare two non-optional tree nodes
default:
return false
}
}
}
But as Leo Dabus said, I'd just conform to Equatable and not create your own operator. Conforming to existing protocols allows you to use TreeNode with many APIs in the standard library, such as Array.contains(_:).

Normal typed variable cannot compare with nil but a literal can, why is that?

var a = 7
a != nil //Error: value of type 'Int' can never be nil, comparison isn't allowed
7 != nil //--> false
0 > nil //true
-9999999 > nil //true
Question Very much like the code above, OK I got it from the compiler that it cannot compare a with nil, but why a literal 7 can compare with nil. I mean 7 is the created existing number, it can also never be nil, isn't it?
BTW
When I try the -9999999 > nil that also returns true, I finally get started to know the nil in the right way. 10 min ago I still think -1 < nil would return true. Would be nice if you can tell me a bit more about nil. What is it? It is not a pointer (different with Object-C). Is it just simply noting?
This is from the Standard Library
/// A type that can represent either a `Wrapped` value or `nil`, the absence
/// of a value.
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
The point to look out for is that Optional can be nil literal convertible.
Second this is how the comparision is defined in the Swift library which can be seen on github on this link:Github Swift Optional Implementation Code
public func < <T : Comparable> (lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
public func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l > r
default:
return rhs < lhs
}
}
Which when combined can be used to reason your code.
The strategy is as follows:
0 > nil
0 is implicity deduced to Optional or Int?.
Swift Optinal has < which says if the lhs is nil then return true
Swift Optional has > which says if the rhs is nil then return true
To simplify step 2 and 3: nil is always smaller than .some
The comparision of the >/< doesnot take into account the element type when either side is nil.
And in the case of
var a = 7
a != nil
Its obvious that Int cannot be compared with Optional.None and this is exactly what that compiler shouts out.
Hope this explains it.

Does an equality operator imply forced unwrapping?

Note the last two lines:
var optionalString: String? // nil
optionalString == "A" // false
//optionalString! == "A" // EXC_BAD_INSTRUCTION
optionalString = "A" // “A”
optionalString == "A" // true
optionalString! == "A" // true
Does that mean that we are not required to unwrap an optional when comparing?
This is the definition of the == operator that looks like it gets used in this case:
public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
You see that both the first argument (lhs) and the second argument (rhs) have to be of the same type, T?. Since the first argument (optionalString) is String?, I think the second argument is cast to String? too, which makes the comparison work.
I think this proves the idea:
func testCasting<T: Equatable>(a: T?, b: T?) {
print(a, b) // Optional("A") Optional("A")
}
var optionalString: String? = "A"
testCasting(optionalString, b: "A")
In the second argument you pass a literal A that gets wrapped in an optional to make the types check. The code compiles and runs.
Note that this is completely different from unwrapping the first argument implicitly. That wouldn’t be a safe operation and would undermine the whole point of optionals.
Apparently one can compare any optional to nil.
optionalString == nil
Which is true if an optional is nil now.
Even this works:
if "A" != nil {
print("A")
}

Swift error comparing two arrays of optionals

I get a compilation error in the next Swift code
var x:Array<Int?> = [1,2]
var y:Array<Int?> = [1,2]
if x == y { // Error
}
If both arrays are Array<Int> it works fine, but if at least one of them is optional it throws an error like the next:
Binary operator '==' cannot be applied to two Array<Int?> operands
I filed a bug report months ago but I had no answer. It still occurs in Swift 1.2.
Why is this happening?
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