Binary operator '==' cannot be applied to two operands
Asked Answered
B

2

30

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
}
Bathe answered 6/1, 2016 at 18:59 Comment(6)
Maybe you need to place the == function inside the class definition in order for it to conform to the Equatable protocol.Ariew
@MikeAtNobel Nope the place of the implementation is good.Deplume
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 equatableDeplume
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.Bathe
It looks remotely similar to #33378261: 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).Kittenish
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...Bathe
K
29

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]]() )
Kittenish answered 6/1, 2016 at 19:26 Comment(5)
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.Bathe
@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?Dirigible
@dfri: There is a public func ==<Key : Equatable, Value : Equatable>(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.Kittenish
@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 ...Ichnography
@user3441734: You are completely right, that is a good point.Kittenish
D
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.

Dirigible answered 6/1, 2016 at 19:16 Comment(6)
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.Bathe
@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.Dirigible
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.Ichnography
@Ichnography 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).Dirigible
@dfri are you sure that Dictionary conforms to Equatable ?Ichnography
@Ichnography 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.Dirigible

© 2022 - 2024 — McMap. All rights reserved.