Here’s a very simplified example on how method invocation works:
class A {
override def equals(that: Any): Boolean = true
}
class B {
// program works the same even if below code line is commented out
override def equals(that: Any): Boolean = false
}
val a = new A
val b = new B
println(a.equals(b)) // prints: true
println(b.equals(a)) // prints: false
// upcasting to 'Object" doesn't change anything in the output
// because 'equals' method is non-static
// so the same method implementation will be invoked
val o1: Object = a
val o2: Object = b
println(o1.equals(o2)) // prints: true
println(o2.equals(o1)) // prints: false
equals is a method just like any other and follows the same rules. You don’t get automatic symmetry for any custom method implementation, so why equals should automatically get one?
== operator in Scala delegates to equals after checking for nulls. == operator in Java is equivalent to eq method in Scala. Default equals is equivalent to eq (except that equals can throw NullPointerException).
But then I won’t get pattern matching either. My applications uses pattern matching heavily on BddNode. Perhaps I want to avoid the case class, but instead write an unapply method?
So the method which is called is always computed according to the runtime class of the value to the left of the dot. However, this is not the case for the rest of the method arguments. In CLOS the same rules apply to all the arguments, even the first (the object to the left of the dot, as there is no left-of-the-dot in CLOS).
class X {
def m(x:X):Int = 1
}
class Y extends X {
override def m(x:X):Int = 2
def m(y:Y):Int = 3
}
val x:X = new X
val y:Y = new Y
val z:X = new Y
List(
List(x.m(x), // 1
x.m(y), // 1
x.m(z)), // 1
List(
y.m(x), // 2
y.m(y), // 3
y.m(z)), // 2, in CLOS this would be 3
List(
z.m(x), // 2
z.m(y), // 2 -- I fail to understand why this returns 2, while y.m(y) returns 3
z.m(z))) // 2
If I change this to a non-case class but with an unapply method, then can I be sure that equivalence checking within hash lookups NEVER traverses the tree of objects? Something like hash.get((bdd1,bdd2)) or hash((bdd1,bdd2)) = bdd3, I never want that to descend the BddNode object stores in the instance variables of bdd1 and bdd2. And if I create a set Set(bdd1,bdd2) then the equivalence check never looks at the instance variables of bdd1 and bdd2 ?
If I am the only one calling ==, then I can of course just call eq instead. But the point of providing a protocol for the class is that other function (that I didn’t write) may call ==.
I think this is perfectly fine if you want case-class advantaces (pattern-matching, avoid new() etc.) but want “reference-equalty”. I’d write equals() as follows tho:
override def equals(that: Any): Boolean = this eq that
Both the mutable and immutable HashMap in 2.12 and earlier perform equality checks once you hit the right hash bucket. The completely new implementations in 2.13 always compare the hashcodes first. This should lower the number of equality checks.
I was somewhat imprecise when writing that statement. It’s true if you don’t combine overloading with upcasting (unless I forgot something) and can be false otherwise. That other discussion clears thing up.