Why Is Equatable Not Defined For Optional Arrays

Why is Equatable not defined for optional arrays

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
}
}

Why doesn't array conform to Equatable, when its items are Equatable in Swift?

Swift 4.1 update:

With the introduction of conditional conformance in Swift 4.1, Array now conforms to Equatable, so the issue should be resolved without the need to resort to any workarounds.

Also, Swift now allows a type to automatically synthesize Equatable conformance, provided all its members are Equatable, simply by declaring Equatable conformance as part of the original type definition (not an extension) but without implementing any of its requirements. This works for enums provided associated values, if any, are Equatable.

The code from this question can now be written much more concisely as below:

import Foundation

struct Post: Equatable {
let text: String
}

enum Result<T>: Equatable where T: Equatable {
case success(result: T)
case error
}

This code will pass all the tests specified in the question:

func test() {

// Test 1: Check Post type for equality: OK
let post1 = Post(text: "post")
let post2 = Post(text: "post")

if post1 == post2 {
print("equal posts")
}

// Test 2: Check [Post] type for equality: OK
let arrayOfPosts1 = [post1, post2]
let arrayOfPosts2 = [post1, post2]

if arrayOfPosts1 == arrayOfPosts2 {
print("equal arrays of post")
}

// Test 3: Check Result<Post> type for equality: OK
let result1 = Result<Post>.success(result: post1)
let result2 = Result<Post>.success(result: post2)

if result1 == result2 {
print("equal results of post")
}

// Test 4: Check Result<[Post]> type for equality: OK
let arrayResult1: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts1)
let arrayResult2: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts2)

if arrayResult1 == arrayResult2 {
print("equal results of array of posts")
}
}

Here is the output:

test()
/*
prints:
equal posts
equal arrays of post
equal results of post
equal results of array of posts
*/

How to extend protocol Optional, where Wrapped item is Array of Equatable generic elements?

Cristik provided a good solution. An alternative is to write an extension to an optional collection of equatable elements:

extension Optional where Wrapped: Collection, Wrapped.Element: Equatable {
func foo() { }
}

This would be applicable to arrays, array slices, and other collections.

Depending on what the extension does, you may want to use a combination of Collection, MutableCollection, BidirectionalCollection, RandomAccessCollection.

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

Swift, Equatable protocol bug?

The reason why it does not work is there is no == operator defined for arrays with optional elements, only for non-optional elements:

/// Returns true if these arrays contain the same elements.
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool

You can provide your own:

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

for index in 0..<lhs.count {
if lhs[index] != rhs[index] {
return false
}
}

return true
}

Swift equality operator on nested arrays

Update: Conditional conformance has been implemented in Swift 4.1. In particular:

The standard library types Optional, Array, and Dictionary now conform to the Equatable protocol when their element types conform to Equatable. ...

(from the Swift CHANGELOG).

Arbitrarily nested arrays of Equatable elements are Equatable now
and can be compared with ==. Your code

var x: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
var y: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
x == y

compiles in Xcode 9.3 if Simple is Equatable.


(Old answer:)
The reason is similar as in Why is Equatable not defined for optional arrays. Arrays can be compared with == if the element type is Equatable:

/// Returns true if these arrays contain the same elements.
public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool

That's why

var a: [Simple] = [Simple(message: "a")]
var b: [Simple] = [Simple(message: "a")]
a == b // -> true

compiles.

But even for equatable types T, Array<T> does not conform to the Equatable protocol, compare Why can't I make Array conform to Equatable?. Therefore, in

var x: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
var y: [[Simple]] = [[Simple(message: "a")], [Simple(message: "b")]]
x == y // -> ERROR! Binary operator '==' cannot be applied to two '[[Simple]]’ operands

x and y are arrays with the element type [Simple] which does
not conform to the Equatable protocol, and there is no
matching == operator.

You could define a generic == operator for simply nested arrays as

func ==<Element : Equatable> (lhs: [[Element]], rhs: [[Element]]) -> Bool {
return lhs.count == rhs.count && !zip(lhs, rhs).contains {$0 != $1 }
}

or more simply (as suggested by @kennytm):

func ==<Element : Equatable> (lhs: [[Element]], rhs: [[Element]]) -> Bool {
return lhs.elementsEqual(rhs, by: ==)
}

This makes x == y compile and work as expected. At present, there seems
to be no way to define a == operator on arbitrarily nested arrays.



Related Topics



Leave a reply



Submit