Swift error comparing two arrays of optionals - swift

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

Related

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(_:).

Swift optionals and equality operator

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

Why is Equatable not defined for optional arrays

Can someone give me a good reason for why this doesn't work:
let a: [Int]? = [1]
let b: [Int]? = nil
a == b
This would be my proposed (if inelegant) solution. But it's trivial, so I feel like I'm missing a good reason why this isn't implemented.
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
if let lhs = lhs, let rhs = rhs {
return lhs == rhs
}
else if let _ = lhs {
return false
}
else if let _ = rhs {
return false
}
return true
}
Update: Conditional conformance has been implemented in Swift 4.1. Arrays and optionals of Equatable elements are themselves
Equatable now, and your code
let a: [Int]? = [1]
let b: [Int]? = nil
a == b
compiles and works as expected in Xcode 9.3. The workarounds are not
needed anymore.
(Old answer:)
Optionals can be compared only if the underlying wrapped type is equatable:
public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
Now Arrays can be compared if the element type is equatable:
/// Returns true if these arrays contain the same elements.
public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool
but even for equatable types T, Array<T> does not conform to the Equatable protocol.
At present, this is not possible in Swift, see for example
Why can't I make Array conform to Equatable? for a discussion
in the Apple developer forum. This change with the implementation
of SE-0143 Conditional conformances
in Swift 4.
Your implementation looks correct, here is a possible different one
using switch/case with pattern matching:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?) : // shortcut for (.Some(l), .Some(r))
return l == r
case (.None, .None):
return true
default:
return false
}
}

Comparing optional arrays

Running the following code snippet in the playground gives an error:
let a: [Int]? = [1,2]
let b: [Int]? = [1,2]
a == b // value of optional type '[Int]?' not unwrapped; did you mean to use '!' or '?'?
While doing something similar for a 'simpler' optional type works:
var x: Int? = 10
var y: Int?
x == y // false
What is the reasoning behind the first case, of optional arrays, not being allowed? Why can't Swift first see if either side if nil (.None) and then if they are not, do the actual array comparison.
The reason it works for simpler types is because there is a version of == that is defined for optionals that contain types that are Equatable:
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
But while Int is Equatable, Array is not (because it might contain something that is not equatable - in which case how could it be). All Equatable things have an == operator, but not all things with an == operator are Equatable.
You could write a special-case version of == specifically for optional arrays containing equatable types:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs,rhs) {
case (.Some(let lhs), .Some(let rhs)):
return lhs == rhs
case (.None, .None):
return true
default:
return false
}
}
You could also generalize this to cover any collection containing equatable elements:
func ==<C: CollectionType where C.Generator.Element: Equatable>
(lhs: C?, rhs: C?) -> Bool {
switch (lhs,rhs) {
case (.Some(let lhs), .Some(let rhs)):
return lhs == rhs
case (.None, .None):
return true
default:
return false
}
}
adding swift 3 version of Airspeed's answer:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs,rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
func ==<C: Collection where C.Iterator.Element: Equatable>(lhs: C?, rhs: C?) -> Bool {
switch (lhs,rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
Swift 4.1
Update: The missing functionality has been implemented in Swift 4.1.
Your code
let a: [Int]? = [1,2]
let b: [Int]? = [1,2]
a == b
compiles and works as expected since Xcode 9.3 (Swift 4.1).
Old answer
The easiest is not to use optional array and use an empty array ([]) instead of nil - in case you don't need to distinguish between those two cases.
let a = [1,2]
let b = [1,2]
let c = []
a == b
b != c
It worked in my case when I was writing Equatable extension for a struct. Instead of using property categories: [Category]? I have just changed it to categories: [Category] and parsed missing categories as empty array ([]).

How to make a function operate on a Sequence of Optional values?

How does one specify that a function should operate on a sequence of optional values in Swift? For example, I want to make a function like this, which works for an Array of Optional values, for sequences.
// Given an array of optional values, return the first one with a value, if any
func firstValue<E>(ary: [E?]) -> E? {
for e in ary {
if let x = e {
return x
}
}
return nil
}
What I was hoping would work, but doesn't, because because there is no such thing as OptionalType):
func firstValue<C: SequenceType where C.Generator.Element: OptionalType>(seq: C) -> C.Generator.Element {
var g = seq.generate()
while let e = g.next() {
return e
}
return nil
}
Try this:
func firstValue<E, S: SequenceType where S.Generator.Element == Optional<E> >(seq: S) -> E? {
var g = seq.generate()
while let e:Optional<E> = g.next() {
if e != nil {
return e
}
}
return nil
}
let a:[Int?] = [nil,nil, 42, nil]
println(firstValue(a)) // -> 42 as Int?
I tested with Xcode Version 6.1.1 (6A2006) and Version 6.2 (6C86e)
Note
Without :Optional<E> in while condition, the compiler crashes.
And if we declare the function like this, the compiler clashes on some environment.
func firstValue<S: SequenceType, E where S.Generator.Element == Optional<E> > {
// ^^^^^^^^^^^^^^^^^^ replaced E and S
I think these are compiler bug. Please see the comments below.
There are two operations needed for the sequence elements:
Check if an element is nil or not, and
Create a nil value of the appropriate type as the default return value if nothing as was found.
For #2 we can use the fact that enum Optional conforms to the NilLiteralConvertible
protocol. For #1 I have defined a NilComparable protocol and made
enum Optional conform to it:
protocol NilComparable {
func isNil() -> Bool
}
extension Optional : NilComparable {
func isNil() -> Bool { return self == nil }
}
Now we can define a function for all sequences whose elements conform
to NilComparable and NilLiteralConvertible. All sequences of optionals
fall into this category:
func firstValue<C: SequenceType where
C.Generator.Element : NilComparable,
C.Generator.Element : NilLiteralConvertible
>(seq: C) -> C.Generator.Element {
var gen = seq.generate()
while let elem = gen.next() {
if !elem.isNil() {
return elem
}
}
return nil // Here NilLiteralConvertible is used.
}
Example:
let arr = [nil, nil, Optional(1), Optional(2)] // Type is [Optional<Int>]
println(firstValue(arr)) // Output: Optional(1)
Update: There is already a function
func !=<T>(lhs: T?, rhs: _OptionalNilComparisonType) -> Bool
which compares any optional value with nil, so the above protocol can be simplified
to
protocol NilComparable {
func !=(lhs: Self, rhs: _OptionalNilComparisonType) -> Bool
}
extension Optional : NilComparable { } // Already conforming
Then we can write if elem != nil { ... } instead of if !elem.isNil() { ... }
in the function.
A possible disadvantage is that _OptionalNilComparisonType is not officially
documented.
Remark: I tried to declare the function as
func firstValue<C: SequenceType, E where C.Generator.Element == Optional<E> >(seq: C) -> E? {
// ...
}
but that actually caused the compiler to crash. I don't know if this should compile.