How to set Equatable in protocols

Hello I am using Term protocol as a Type for my structs so I can have a Statement struct with terms collection where I could store the rest of my structs. What I would like to do is, I would like to check for equality for the term in the array and the argument I pass in the function in the bottom. I know the WWDC session Building Better Apps with Value Types talking about Equatable protocol being implementing on a struct however in this instance I do have Term which is already a protocol. How would it work in this case?


Thanks in advance.


import Foundation
protocol Term {
  var value: String { get }
}
extension Term  {}
struct Statement  {
  var terms: [Term] = []
  mutating func addItem(term: Term) {
    terms.append(term)
  }
}
struct Fraction: Term {
  let numerator: Statement
  let denominator: Statement
  let value: String
}
struct Exponent: Term {
  let base: String
  let power: Statement
  let value: String
}
struct Variable: Term {
  let value: String
}
struct Operator: Term {
  let value: String
}
struct Number: Term {
  let value: String
}


func highlightTerm(term:Term) {
    for(_, localTerm) in EnumerateSequence(statement.terms) {
          //how can I check if
          //term == localTerm?
    }
}


protocol Equatable {
  func ==(lhs: Self, rhs: Self) -> Bool
}
extension Term: Equatable { }
func ==(lhs: Term, rhs: Term) -> Bool {
  return lhs.value == rhs.value
}

currently I get Extension of protocol 'Term' cannot have an inheritance clause at line 47

Define protocol Term: Equatable instead of adding extension Term: Equatable.

This time I get:


Protocol 'Term' can only be used as a generic constraint because it has Self or associated type requirements.


protocol Term: Equatable {
  var value: String { get }
}
extension Term  {}
protocol Equatable {
  func ==(lhs: Self, rhs: Self) -> Bool
}
func ==(lhs: Term, rhs: Term) -> Bool {
  return lhs.value == rhs.value
}

I ended up doing this:


extension Fraction : Equatable {}
func == (lhs: Fraction, rhs: Fraction) -> Bool {
  return lhs.value == rhs.value
}
extension Exponent : Equatable {}
func == (lhs: Exponent, rhs: Exponent) -> Bool {
  return lhs.value == rhs.value
}
extension Variable : Equatable {}
func == (lhs: Variable, rhs: Variable) -> Bool {
  return lhs.value == rhs.value
}
extension Operator : Equatable {}
func == (lhs: Operator, rhs: Operator) -> Bool {
  return lhs.value == rhs.value
}
extension Number : Equatable {}
func == (lhs: Number, rhs: Number) -> Bool {
  return lhs.value == rhs.value
}
extension Equatable where Self : Term {
  func isEqualTo(other: Term) -> Bool {
    guard let o = other as? Self else { return false }
    return self == o
  }
}

Despite your effort, isEqualTo cannot be applied to Term type values.

statement.terms[0].isEqualTo(statement.terms[1]) //'Term' does not have a member named 'isEqualTo'

And Term type itself does not conform to Equatable.

statement.terms.indexOf(Operator(value: "+")) //cannot invoke 'indexOf' with an argument list of type '(Operator)'
//note: expected an argument list of type '(@noescape (Self.Generator.Element) -> Bool)'


If you just want to give a common definition of `==` to all Term-conforming types, a single generic operator will do.

extension Fraction : Equatable {}
extension Exponent : Equatable {}
extension Variable : Equatable {}
extension Operator : Equatable {}
extension Number : Equatable {}
func == <T: Term>(lhs: T, rhs: T) -> Bool {
    return lhs.value == rhs.value
}


Till now, I couldn't have found any way to make Term type conform to Equatable, and seems impossible with the current Swift type system.

As OOPer wrote, Swift's current type system doesn't allow protocol `Term` to extend `Equatable` if you want to keep on using `Term` as a type. I just wanted to point out that for your use case, a more functional design would definitely make more sense. I would define `Term` as an algebraic datatype using a recursive enum. Then it's not a protocol anymore, but it can implement `Equatable` and the code looks much more compact:


struct Statement {
  var terms: [Term] = []
  mutating func addItem(term: Term) {
    terms.append(term)
  }
}
indirect enum Term: Equatable {
  case Fraction(numerator: Statement, denominator: Statement, value: String)
  case Exponent(base: String, power: Term, value: String)
  case Variable(value: String)
  case Operator(value: String)
  case Number(value: String)
}
func ==(lhs: Term, rhs: Term) -> Bool {
  switch (lhs, rhs) {
    case (.Fraction(_, _, let v1), .Fraction(_, _, let v2)): return v1 == v2
    case (.Exponent(_, _, let v1), .Exponent(_, _, let v2)): return v1 == v2
    case (.Variable(let v1), .Variable(let v2)): return v1 == v2
    case (.Operator(let v1), .Operator(let v2)): return v1 == v2
    case (.Number(let v1), .Number(let v2)): return v1 == v2
    default: return false
  }
}


An alternative could be to expose `value` as a property of `Term`. This makes `==` more compact, but this will only work if the values of the different `Term` variants are non-overlapping. Otherwise, a variable 'foo' and an operator 'foo' are considered equal. This was OOPer's original idea.


indirect enum Term: Equatable {
  case Fraction(numerator: Statement, denominator: Statement, value: String)
  case Exponent(base: String, power: Term, value: String)
  case Variable(value: String)
  case Operator(value: String)
  case Number(value: String)
  var value: String {
    switch self {
      case .Fraction(_, _, let v): return v
      case .Exponent(_, _, let v): return v
      case .Variable(let v): return v
      case .Operator(let v): return v
      case .Number(let v): return v
    }
  }
}
func ==(lhs: Term, rhs: Term) -> Bool {
  return lhs.value == rhs.value
}

This new `==` operator is not semantically equivalent to the former definition. It doesn't distinguish between the different `Term` variants. For instance, a variable named '$' and an operator named '$' are now considered equivalent.

You'd better make it a try yourself:

print(Operator(value: "$") == Variable(value: "$")) //error: binary operator '==' cannot be applied to operands of type 'Operator' and 'Variable'

Interesting. I was assuming that


func == <T: Term>(lhs: T, rhs: T) -> Bool {
  return lhs.value == rhs.value
}


would result in all `Term` combinations being comparable. It seems like the compiler also doesn't like the case where the static type is equivalent:


let t1: Term = Operator(value: "$")
let t2: Term = Variable(value: "$")
print(t1 == t2)


Now I get "Binary operator '==' cannot be applied to two Term operands". But isn't this exactly the problem ilteris wants to solve?


Since I either don't understand polymorphic functions in Swift, or there's a bug somewhere, I tried the following: I added, in addition to the polymorphic `==`, a monomorphic function `==` just for `Term`. Now both your code and my code compiles and behaves in the way I thought it should behave.


let t1: Term = Operator(value: "$")
let t2: Term = Variable(value: "$")
print(t1 == t2)  // prints "true"
print(Operator(value: "$") == Variable(value: "$"))  // prints "true"


I didn't do more research but it does look like that type inference for polymorhic functions is broken. It is my understanding that `func == <T: Term>(lhs: T, rhs: T) -> Bool { ... }` defines an implementation for `==` for all static types `T` which are subtypes of `Term`. This doesn't seem to be the case. Even in your example, the compiler needs to realize that for `T == Term`, the function is applicable to `Operator` and `Variable`.

It is really confusing whether we should write:

func == <T: Term>(lhs: T, rhs: T) -> Bool {

or:

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


But isn't this exactly the problem ilteris wants to solve?

His intention would be explained by himself.

And I have this code in the opening post in mind:

func highlightTerm(term:Term) {
    for(_, localTerm) in EnumerateSequence(statement.terms) {
          //how can I check if
          //term == localTerm?
    }
}


To make this sort of code work:

var statement = Statement()
statement.addItem(Variable(value: "x"))
statement.addItem(Number(value: "0"))
statement.addItem(Operator(value: "+"))
func highlightTerm(term:Term) {
    for localTerm in statement.terms {
        if term == localTerm {
            print("<<\(localTerm)>>")
        } else {
            print(localTerm)
        }
    }
}
highlightTerm(Number(value: "0"))

The <T: Term> version of `==` cannot be used.

This is exactly the point I was trying to make. And if you now take your latest code (the one that uses the monomorphic `==`), it will equalize operators "$" with variables "$".

Accepted Answer

I have another option:

func == (lhs: Term, rhs: Term) -> Bool {
    return lhs.dynamicType == rhs.dynamicType && lhs.value == rhs.value
}


With this `==`, try:

print(Operator(value: "$") == Variable(value: "$"))

or:

highlightTerm(Number(value: "0"))
highlightTerm(Variable(value: "0"))

Great, this is a nice solution! So, as a summary, I believe the best that can be done with Swift 2 today is:

  • Make all implementations of `Term` conform to `Equatable` via the extensions for `Equatable` and the polymorhic `==`
  • Provide a monomorphic `==` function (your last version) that works for `Term` values in general (and that can be used in functions like `highlightTerm`)
  • If there are use cases that require `Term` to be `Equatable`, then the usual workaround via a generic `AnyTerm` implementation needs to be done.

Gentlemen,


I didn't forget this post. Thanks so much for your super useful discussions. I had to solve different problems in the project and now I came back and used OOPer's solution for now and it works for my purposes.


ObjectHub, your solution for making Term is really elegant. In fact I am starting to think that using protocols for Type is not a good idea at all in general. My next task is going to replace the protocol Term with your recommendation. I will let you know how that goes.


Thanks so much for your time again, I really appreciate it!

How to set Equatable in protocols
 
 
Q