Stefan Arentz
Stefan Arentz

Reputation: 34945

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.

Upvotes: 19

Views: 2506

Answers (3)

Marián Černý
Marián Černý

Reputation: 15748

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 ([]).

Upvotes: -1

Vic Zhou
Vic Zhou

Reputation: 1311

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

Upvotes: 8

Airspeed Velocity
Airspeed Velocity

Reputation: 40965

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

Upvotes: 31

Related Questions