Eli-Golin
10/18/2016 - 6:06 AM

Defining 'Object's equality'

Defining 'Object's equality'

One critical reason to prefer immutability is the ease of equality implementation.

class Point2(var x:Int, var y:Int) {
  def move (mx:Int,my:Int): Unit = {
    x = x + mx
    y = y+ my
  }
  override def hashCode:Int = y + (31 * x)
}

Let's suppose we want to map each point to some name.
val x = new Point2(1,1)
val y = new Point2(2,1)
val map  = HashMap(x -> "HAI", y -> "ZOMG")
val z = new Point2(1,1)
map(z) //java.util.NoSuchElementException

This is hapenning since we did not implement our own equals method, and as in Java 
the default equality of instances is checked by their address in memory.

Since Scala's AnyVal has no equivalent in Java as appose to AnyRef which is just 
an alias to java.lang.Object, Scala has it's own convention for hashCode and equlas
which unifies all types:
The == an alias method for equals
The ## an alias method for hashCode


Let's implement our one equals

class Point2(var x:Int, var y:Int) {
  def move(mx: Int, my: Int) : Unit = {
    x = x + mx
    y = y + my
  }

  override def hashCode(): Int = y + (31*x)

  def canEqual(that: Any): Boolean = that match {
    case p: Point2 => true
    case _ => false
  }

override def equals(that: Any): Boolean = {
  def strictEquals(other: Point2) =
    this.x == other.x && this.y == other.y
    that match {
      case a: AnyRef if this eq a => true
      case p: Point2 => (p canEqual this) && strictEquals(p)
      case _ => false
    }
  }
}

In this case we will have a problem if after inserting an object into a Map
we will modify it's fields. This way we will change the outcome of it's ## method
and thus won't ffind it in the Map

Thus:
* If two objects are equal, they should have the same hashCode.
* A hashCode computed for an object won’t change for the life of the object.
* When sending an object to another JVM, equality should be determined using
  attributes available in both JVMs.
  
Polymorpfhic Equality:
* When we have a polymorphic object with more than one level of concrete implementations
  it is harder to implement the equlas method the right way.
  
Example:
trait InstanteneousTime {
  val repr:Int //number representing seconds since epoch start
  override def equals(other:Any) = other match {
    case that:InstanteneousTime =>
      if(this eq that)
        true
      else {
        that.## == this.## && //A good equals method uses hashCode for an early equality condition
        repr == that.repr
      }
    case _ => false
  }
  override def hashCode():Int = repr.##
}

trait Event extends InstanteneousTime {
  val name:String
  override def equals(other:Any):Boolean = other match {
    case that:Event =>
      if(this eq that)
        true
      else {
        repe == that.repr && name == that.name
      }
    case _ => false
  }
}
val x = new InstanteneousTime {var repr = 2}
val y = new Event {val name = "Test Event"; val repr = 2}

y == x //false
x == y //true 

So the right implementation is as follows:

trait InstantaneousTime extends Equals {
  val repr: Int
  override def canEqual(other: Any) = other.isInstanceOf[InstantaneousTime]

  override def equals(other: Any) : Boolean = other match {
    case that: InstantaneousTime =>
      if(this eq that) true else {
        (that.## == this.##) &&
        (that canEqual this) &&
        (repr == that.repr)
      }
    case _ => false
  }
  override def hashCode(): Int = repr.hashCode
}

trait Event extends InstaneousTime {
  val name: String
  override def canEqual(other:Any) = other.isInstanceOf[Event]
  override def equals(other:Any) = other match {
    case that: Event =>
     if(this eq that)
      true
    else {
      (that canEqual this) &&
      (repr == that.repr) &&
      (name == taht.name)
    }
    case _ => false
  }
}

* When overriding equality of a parent class, also override canEqual