> > > The real problem is that equals and polymorphism
> don't
> > > mix.
> > The solution outlined in "Programming in Scala",
> chapter
> > 28 works well.
Chapter 28 of the Scala Book is great, particularly since I am a fan of multiple dispatch and the suggested solution is to hand code multiple dispatch :). Quick recap of chapter 28 the recommended solution is:
class Point(val x: Int, val y: Int) {
override def hashCode = 41 * (41 + x) + y
override def equals(other: Any) = other match {
case that: Point => (that canEqual this) && (this.x == that.x) && (this.y == that.y)
case _ => false
}
def canEqual(other: Any) = other.isInstanceOf[Point]
}
class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) {
override def hashCode = 41 * super.hashCode + color.hashCode
override def equals(other: Any) = other match {
case that: ColoredPoint => (that canEqual this) && super.equals(that) && this.color == that.color
case _ => false
}
override def canEqual(other: Any) = other.isInstanceOf[ColoredPoint]
}
The crucial bit of the solution is the call:
that canEqual this
which you note is *not*:
this canEqual that
IE it is a second dispatch. The solution works and I thought it was widely known, but maybe it isn't widely know since people on this forum have bought it up in the context suggesting that it is novel (there may be some novelty in that canEqual only tests types, but essentially the method is the same as double dispatch or the Visitor pattern). There are a number of limitations:
1. It quickly gets out of hand for multiple arguments, you need can1, can2, etc which rotate the arguments.
2. It can only do the stricter comparison (page 578 says "Making the equals relation more general seems to lead to a dead end").
3. The code is verbose and ugly.
Lets pretend that Scala got multiple dispatch and a method declared with multidef defined a multiple dispatch method, then the above example would become:
class Point(val x: Int, val y: Int) {
override def hashCode = 41 * (41 + x) + y
override multidef equals(this, that: Any) = false
override multidef equals(this, that: Point) = (x == that.x) && (y == that.y)
}
class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) {
override def hashCode = 41 * super.hashCode + color.hashCode
override multidef equals(this, that: ColordePoint) = (x == that.x) && (y == that.y) && (this.color == that.color)
}
Which I think is a lot clearer and address points 1 and 3 that I raised above. With multiple dispatch you can also do a more general comparison (point 2 above), e.g. suppose you want Point to be a Black ColoredPoint, then you can:
class ColoredPoint(x: Int, y: Int, val color: Color.Value) extends Point(x, y) {
override def hashCode = 41 * super.hashCode + color.hashCode
override multidef equals(this, that: ColoredPoint) = (x == that.x) && (y == that.y) && (this.color == that.color)
override multidef equals(this, that: Point) = (x == that.x) && (y == that.y) && (this.color == Black)
override multidef equals(that: Point, this) = (x == that.x) && (y == that.y) && (this.color == Black)
}
So in summary, the suggested solution in chapter 28 is a hand coded form of limited multiple dispatch. Yes it does overcome some of the limitations of pattern matching, but not all (unlike true multiple dispatch) and it is extra tricky code to write.