This question was previously asked in scala-user mailing-list without a confirmed answer.
scala> val T = new Pair(1, 2){
override def equals(obj:Any) = obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1}
}
T: (Int, Int) = (1,2)
scala> T match {
case (1, 1) => println("matched")
case _ => println("not matched")
}
not matched
scala> (1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
not matched
scala> T == (1, 1)
res15: Boolean = true
I thought a constant(val) pattern matching result depends on the return value of "equals", but the results show that it is not the case, then what is the criteria?
Someone had suggested that case (1, 1) =>
is an extractor pattern and uses Tuple2.unapply
instead. so I tried these:
scala> Pair.unapply(T)
res1: Option[(Int, Int)] = Some((1,2))
scala> Pair.unapply(T).get == (1, 1)
res2: Boolean = true
Can anyone please explain why ==
get true but I cannot make them match?
All classes in Java inherit from the Object class, directly or indirectly (See point 1 of this). The Object class has some basic methods like clone(), toString(), equals(),.. etc. We can override the equals method in our class to check whether two objects have same data or not.
"If two objects are equal using Object class equals method, then the hashcode method should give the same value for these two objects." So, if in our class we override equals() we should override hashcode() method also to follow this rule.
The String class overrides the equals method it inherited from the Object class and implemented logic to compare the two String objects character by character. The reason the equals method in the Object class does reference equality is because it does not know how to do anything else.
The equals() method compares two strings, and returns true if the strings are equal, and false if not.
The problem with your example is that you only override the equals
method of the anonymous class you define your specific tuple to be part of. Let's take a closer look at what you are doing when running the code you have given here.
val p = new Pair(1, 2) {
override def equals(obj:Any) = {
obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
}
What Scala does here is it creates a new anonymous class which extends Pair
and overrides its equals. So this is equivalent to running the following code:
class Foo extends Pair(1,2) {
override def equals(obj:Any) = {
obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
}
val p = new Foo
And here is where you can see exactly where the problem lies! The definition of equals
is not symmetric. p == (1,1)
evaluates to true
and (1,1) == p
evaluates to false
! This is because the former is equivalent to p.equals((1,1))
while the latter is equivalent to (1,1).equals(p)
. In the example you have given, it doesn't work because the object in the case is compared with the object being matched, and not the other way around. Therefore, as you have indicated, Pair.unapply(p).get == (1, 1)
evaluates to true
, however (1,1) == Pair.unapply(p).get
evaluates to false
, and it seems that the latter is the one used when matching.
However, in any case creating an equals that is not symmetric is a really really bad idea as the execution of the code depends on the order you compare objects in. Furthermore, the equals you have defined has a further problem -- it fails with error when you try to compare your p
with any Pair
that is not of type (Int, Int)
. This is because, after type erasure (which is how the JVM implements generics), a Pair
is no longer parametrised by the types of its constituents. Therefore, (Int, Int)
has exactly the same type as (String, String)
and thus, the following code will fail with error:
p == ("foo", "bar")
as Scala will try to cast a (String, String)
to an (Int, Int)
.
If you want to implement this functionality, the easiest thing you could do is to use the pimp my library pattern, pimping a Pair
. However, you shouldn't call your method equals
. Call it something else, like ~=
. I have to be going now, however when I come back I could give you the code for it. It's pretty easy. You should look at the implementation of equals
in a pair and remove the part that compares the second argument :)
I have to say Scala surprised me, again. Some more tests show that the result depends on the running context.
If you run :
object Pair{
def main(args:Array[String]) {
val T = new Pair(1, 2){
override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
(1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
}
}
you get: matched
And if you run:
object Pair extends Application {
val T = new Pair(1, 2){
override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
}
(1, 1) match {
case T => println("matched")
case _ => println("not matched")
}
}
you get : not matched
I have always thought that there is no difference between code in main() methods and code in object body which extends Application trait. Really weird.
Tuples are privileged in the pattern matcher. They're not your everyday classes. It's in the spec, section 8.1.7, "Tuple Patterns".
When you say
(1, 1) match { case T ...
Then equals is being called, on (1, 1), which of course says no thanks, not equal.
When you say
T match { case (1, 1) => ...
Then your equals method is ignored because of the tuple pattern, and T._1 is compared to 1 and T._2 to 1, and again it does not match.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With