Scala Corner Case#3: "object" or "case object" Extends Case Class? It's a Different Story
Case class in Scala is a neat feature, it automatically generates apply, extract and equals functions. But I encountered a strange "equals" behavior today, which wasted me 2 hours to find why.
Define a simple case class "Symbol", then a "VarSymbol" which extends "Symbol" with one more field "name:String". We know you can compare the equality of instances of "VarSymbol" by this "name", Scala automatically gets it right. And of course, a direct instance from "Symbol()" should not equal to any "VarSymbol", since it lacks "name" field, that's right.
case class Symbol() case class VarSymbol(name:String) extends Symbol
Then, I need a singleton object "NoSymbol", I defined it as:
object NoSymbol extends Symbol
I of course thought this "NoSymbol" should not equal any "VarSymbol" instance, or,
NoSymbol == VarSymbol("I'm var")
Should return false. But life is not so straightforward, it returns true in real life !!!
I finally got my code working, by adding a "case" before "object NoSymbol extends Symbol". This "NoSymbol" won't equal any "VarSymbol" now, I'm grad things finally go back in track.
I don't know if it's a Scala bug, or, that's what Scala thinks it should be: all "Symbol" and it's inherited instances should equal "NoSymbol" object. Anyway, here's the whole code for test:
case class Symbol()
case class VarSymbol(name:String) extends Symbol
object NoSymbol extends Symbol
case object CasedNoSymbol extends Symbol
object TestCaseObject {
def test = {
val noSym = NoSymbol
val caseNoSym = CasedNoSymbol
val varSym = VarSymbol("I'm var")
if (noSym == varSym) println("NoSym equals varSym !")
else println("NoSym doesn't equal varSym")
if (caseNoSym == varSym) println("CaseNoSym equals varSym !")
else println("CaseNoSym doesn't equal varSym")
}
}
Run TestCaseObject.test, I got:
NoSym equals varSym ! CaseNoSym doesn't equal varSym
![(please configure the [header_logo] section in trac.ini)](/chrome/site/blog_logo.png)
rss
Comments
The behaviour with object is actually correct behaviour (and this fact should be obvious when you think about it the right way).
You've got case class Symbol. This defines equals because it's a case class. It has an empty argument list, so all Symbols are equal. You extend it without defining a new equals method. Therefore it inherits the equals method. You now pass it a Symbol object (the fact that the object you passed overrides equality is irrelevant), which it declares to be equal because all symbols are equal. Meanwhile VarSymbol? has overridden equals so *doesn't* think it's equal. It's a classic example of equality + subtyping being broken.
There are a bunch of additional related issues here. Hopefully some of them will improve. But basically I would strongly encourage you to not use inheritance of case classes (i.e. one case class extending another), now or ever. Make your base class non-case.
The "Effective Java" book item on equality (item 7 or item 8) is a good read on the subject of equality and subtyping.
However, re-reading your post and that item, I am wondering: what if a class and its subclass (Point and ColorPoint? in the book example) were NEVER equal. This wouldn't break symmetry nor transitivity. What would be lost then?
@David, @Eric, thanks for the explanation and information. When I call "varSym == noSym", it will return false, it hints at that the object NoSym?'s seems to implement "equals" via "instanceOf" instead of "getClass" ?.
Exactly, that's how it's implemented in the Symbol class.
Here is a follow-up from Joshua Bloch on using instanceof vs getClass. This answers my question about the risks of using getClass: http://www.artima.com/intv/bloch17.html.