15

From the source code scala/Equals.scala (here):

package scala
trait Equals extends scala.Any {
  def canEqual(that: scala.Any): scala.Boolean
  def equals(that: scala.Any): scala.Boolean
}

In the documentation, it says:

A method that should be called from every well-designed equals method that is open to be overridden in a subclass.

I randomly picked a class which extends scala.Equals and which is simple enough to understand. I picked scala.Tuple2[+T1, +T2], which extends the trait scala.Product[T1, T2], which in turn extends the trait scala.Product, which in turn extends the trait scala.Equals.

Unfortunately, it seems that because scala.Tuple2 is a case class, the canEqual() and equals() methods are automatically generated and therefore could not be found in the source code scala/Tuple2.scala (here).

My questions are:

  • When is it a good time to extend the trait scala.Equals?
  • How should canEqual() be implemented?
  • What are the best practices (or boilerplate) to use canEqual() in equals()?

Thanks in advance!

PS: In case if it matters, I'm using Scala 2.11.7.

  • I just posted an answer on another question which answers the second and third of your questions. And it appears no one has answered your first question. I would say the answer to your third question is: Always extend `scala.Equals` on any class (but not a case class) where you are implementing any of `equals`, `canEquals`, or `hashCode`. It is crucial for instances of your class to properly behave within the Scala collections library. IOW, you must get your implementations correct in this area or suffer unexpected, and like silent, failures. http://stackoverflow.com/a/56509518/501113 – chaotic3quilibrium Jun 17 '19 at 00:13

1 Answers1

20

The canEquals method is used to cover the expectation that equals should be symmetric - that is, if (and only if) a.equals(b) is true, then b.equals(a) should also be true. Problems with this can arise when comparing an instance of a class with an instance of a sub-class. Eg.

class Animal(numLegs: Int, isCarnivore: Boolean) {
  def equals(other: Any) = other match {
    case that: Animal => 
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore
    case _ => false
  }
}

class Dog(numLegs: Int, isCarnivore: Boolean, breed: String) extends Animal(numLegs, isCarnivore) {
  def equals(other: Any) = other match {
    case that: Dog => 
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore &&
      this.breed == that.breed
    case _ => false
  }
}

val cecil = new Animal(4, true)
val bruce = new Dog(4, true, "Boxer")
cecil.equals(bruce) // true
bruce.equals(cecil) // false - cecil isn't a Dog!

To fix this, ensure the two entities are of the same (sub-)type using canEqual in the definition of equals:

class Animal(numLegs: Int, isCarnivore: Boolean) {
  def canEqual(other: Any) = other.isInstanceOf[Animal]
  def equals(other: Any) = other match {
    case that: Animal => 
      that.canEqual(this) &&
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore
    case _ => false
  }
}

class Dog(numLegs: Int, isCarnivore: Boolean, breed: String) extends Animal(numLegs, isCarnivore) {
  def canEqual(other: Any) = other.isInstanceOf[Dog]
  def equals(other: Any) = other match {
    case that: Dog => 
      that.canEqual(this) &&
      this.numLegs == that.numLegs && 
      this.isCarnivore == that.isCarnivore &&
      this.breed == that.breed
    case _ => false
  }
}

val cecil = new Animal(4, true)
val bruce = new Dog(4, true, "Boxer")
cecil.equals(bruce) // false - call to bruce.canEqual(cecil) returns false
bruce.equals(cecil) // false
Shadowlands
  • 14,994
  • 4
  • 45
  • 43
  • 2
    Thanks for your answer! I think the trick is `that.canEqual(this)` instead of `this.canEqual(that)`, which does the check "in an opposite way" to ensure the validness of `equals()`. Do you know whether the generated case class version follows the same pattern as you have shown here? Thanks! – Siu Ching Pong -Asuka Kenji- Aug 19 '15 at 15:42
  • @SiuChingPong-AsukaKenji- I believe it does, but I don't know for sure. – Shadowlands Aug 19 '15 at 20:46
  • 2
    This answer is incomplete, bordering on incorrect. When overriding `equals`, it is required `hashCode` also be overridden. There are many other nuances to implementing `equals`, `canEquals`, and `hashCode` which I detail here: http://stackoverflow.com/a/56509518/501113 – chaotic3quilibrium Jun 17 '19 at 00:12