29

I have a class with the protocol Equatable. The class looks like this:

class Item: Equatable {

    let item: [[Modifications: String]]

    init(item: [[Modifications: String]]) {
        self.item = item
    }
}

func ==(lhs: Item, rhs: Item) -> Bool {
    return lhs.item == rhs.item
}

But this is giving me the error (see title). The property item was [[String: String]] before and there was no problem and I have no idea how to fix this. I tried googling and searching all over SO but with no luck..

The enum is just a simple basic one:

enum Modifications: Int {
    case Add    = 1
    case Remove = 2
    case More   = 3
    case Less   = 4
}
Henny Lee
  • 2,970
  • 3
  • 20
  • 37
  • Maybe you need to place the `==` function inside the class definition in order for it to conform to the `Equatable` protocol. – dudeman Jan 06 '16 at 19:06
  • @MikeAtNobel Nope the place of the implementation is good. – milo526 Jan 06 '16 at 19:09
  • Does Modifications conform to Equatable? Since you are using it in your array of dictionaries, I can see why it needs to be equatable for your item to be equatable – milo526 Jan 06 '16 at 19:10
  • When its a `[[String: String]]` there's no problem, so 2d dicts should be able to be compared. @milo526: Because the enum has a raw type value, it becomes equatable automatically, if im correct. @Leo Dabus: yes, I'm trying to think of another way but it would be much easier if its an array of dictionaries. – Henny Lee Jan 06 '16 at 19:13
  • It looks remotely similar to http://stackoverflow.com/questions/33377761/swift-equality-operator-on-nested-arrays: You *can* compare two instances of `[Modifications: String]` with `==`, but `[Modifications: String]` does not conform to `Equatable`. Therefore `==` is not defined for an *array* of those dictionaries. – My guess is that is works for `[[String: String]]` because `[String: String]` can be bridged to NSDictionary (which is Equatable again). – Martin R Jan 06 '16 at 19:15
  • Yea, the problem is the array of dictionary of type `[Modifications: String]`. I guess I can still use `[[String: String]` but using 'Modifications.description` as key. Still I hope it's possible to fix this... – Henny Lee Jan 06 '16 at 19:16

2 Answers2

27

Update: SE-0143 Conditional conformances has been implemented in Swift 4.2.

As a consequence, your code does compile now. And if you define Item as a struct

struct Item: Equatable {
    let item: [[Modifications: String]]

    init(item: [[Modifications: String]]) {
        self.item = item
    }
}

then the compiler synthesizes the == operator automatically, compare SE-0185 Synthesizing Equatable and Hashable conformance


(Pre Swift 4.1 answer:)

The problem is that even if == is defined for the dictionary type [Modifications: String], that type does not conform to Equatable. Therefore the array comparison operator

public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool

cannot be applied to [[Modifications: String]].

A possible concise implementation of == for Item would be

func ==(lhs: Item, rhs: Item) -> Bool {
    return lhs.item.count == rhs.item.count 
           && !zip(lhs.item, rhs.item).contains {$0 != $1 }
}

Your code compiles for [[String: String]] – if the Foundation framework is imported, as @user3441734 correctly said – because then [String: String] is automatically converted to NSDictionary which conforms to Equatable. Here is a "proof" for that claim:

func foo<T : Equatable>(obj :[T]) {
    print(obj.dynamicType)
}

// This does not compile:
foo( [[Modifications: String]]() )

// This compiles, and the output is "Array<NSDictionary>":
foo( [[String: String]]() )
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Nice explanation, It's good to know why it did work for `[[String: String]]`. Marking this one as answered since the answer looks more elegant. Now I just have to figure out how to ignore the order of the dictionaries. – Henny Lee Jan 06 '16 at 19:29
  • @MartinR: user3441734 noted in the comments to my answer below that `Dictionary` types does not conform to protocol `Equatable`. You don't happen to know the reason why we still can compare them "off the bat", as `$0 != $1` above (or the `lhsDict != rhs.item[i]` in my solution below)? Is it some automatic conversion much like the case you cover above? – dfrib Jan 06 '16 at 19:50
  • @dfri: There is a `public func ==(lhs: [Key : Value], rhs: [Key : Value]) -> Bool` operator: You can compare dictionaries with `==` if both key and value type are Equatable. It's the same thing as with arrays: you can compare them with `==` if the element type is Equatable. – Martin R Jan 06 '16 at 19:53
  • @MartinR please, note that the 'free bridging' is available with help of Foundation. By the way, your explanation is very clear! I wish I have the same property to explain my thoughts or idea. Unfortanly, I don't have ... – user3441734 Jan 06 '16 at 20:22
  • @user3441734: You are completely right, that is a good point. – Martin R Jan 06 '16 at 20:31
3

In your == function for Item objects, you need to specify further how to compare two types of arrays of dictionaries (specifically, two types of [[Modifications: String]]).

The following working solution compares your item arrays element by element (dictionary by dictionary), and == returns true only if the arrays contain the same number of dictionaries, and if all entries are alike and ordered the same fashion in the array of dictionares

func ==(lhs: Item, rhs: Item) -> Bool {

    if lhs.item.count == rhs.item.count {
        for (i, lhsDict) in lhs.item.enumerate() {
            if lhsDict != rhs.item[i] {
                return false
            }
        }
        return true
    }
    else {
        return false
    }
}

class Item : Equatable {

    let item: [[Modifications: String]]

    init(item: [[Modifications: String]]) {
        self.item = item
    }
}

You probably want to modify this into the form you actually want to use for comparison, but I hope you get the gist of it.

Note also that, if testing this in a playground, it's important that your == function definition func ==(lhs: Item, rhs: Item) -> Bool { .. should precede your class definition, otherwise you will get an error of nonconformance to Equatable.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thanks, this does indeed make `Item` comparable. I was hoping for something simpler though but I guess I'll have to iterate through the array and compare them. – Henny Lee Jan 06 '16 at 19:24
  • @HennyLee perhaps you can come up with a better solution, this mainly shows you an example of how to achieve a _working_ one. E.g. using `zip` and `map` can probably condense the for loop into one line, in the case above where the arrays are of same length. – dfrib Jan 06 '16 at 19:26
  • An Swift Array doesn't conform to Equatable protocol. So, there is your responsibility to make Array<[Modification:String]> to conform to Equatable protocol. How you will do it, is up t o you. Conformance to Equatable means, that operator == is defined. If you want, it can simply return true :-) the way, how your instances of type [Modifications:String] is checked is absolutely under you responsibility. – user3441734 Jan 06 '16 at 19:33
  • @user3441734 you're on point, just note that two dictionaries of type `[Modifications:String]` are comparable directly with `==` (as dictionaries are equatable): the problem here was the array of these dictionaries of non-fundamental key types (as Martin R covered in his answer). – dfrib Jan 06 '16 at 19:37
  • @dfri are you sure that Dictionary conforms to Equatable ? – user3441734 Jan 06 '16 at 19:42
  • @user3441734 You're right here, it seems `Dictionary` does not conform to `Equatable`, so the reason that `==` works on these dictionaries is perhaps some automatic conversion rather than due to conformance to `Equatable`. – dfrib Jan 06 '16 at 19:45