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
Swift 4 Decodable - Dictionary With Enum as Key
Swift - Iboutletcollection Equivalent
What Does the Dollar Sign Do in Swift/Swiftui
Can Swift Return Value from an Async Void-Returning Block
How to Detect Which Skspritenode Has Been Touched
Forced to Cast, Even If Protocol Requires Given Type
Nsfilemanager Fileexistsatpath:Isdirectory and Swift
Why Do Self and Self Sometimes Refer to Different Types in Static Functions
Noop For Swift'S Exhaustive Switch Statements
Iterate Over Collection Two At a Time in Swift
Swiftui Picker Separate Texts For Selected Item and Selection View
Swift 3 How to Display a Confirmation Screen Based on Mfmailcomposeresult Email Screen
Updating Time Text Label Each Minute in Widgetkit
How to Accomplish Where in Query in Cloud Firestore