tags:

views:

191

answers:

4

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?

+9  A: 

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 :)

Flaviu Cipcigan
But, my code has test both directions of match: T match (1, 1) and (1, 1) match T, and at least one should success.
xiefei
+3  A: 

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.

xiefei
Initialization doesn't seem to be a factor, but `T` is a field inside an `object`, and a local variable inside a method.
Daniel
After moving definition of T from main to object, the result does become "not matched". but why?
xiefei
At a glance, it looks like a bug.
extempore
+2  A: 

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.

extempore
Why does the first example of xeifei's answer returns matched, then? I have reproduced that myself.
Daniel
+1  A: 

With the resolution of #3888, I can give a final answer to this question.

  1. T match { case (1, 1) =>

    No, it has nothing to do with unapply. As extempore mentioned, case (1,1) => is a 'Tuple Pattern', an alias for 'Constructor Pattern" of case class Tuple2, It only matches value constructed as Tuple2(1, 1) or Pair(1, 1).

    What really concerns unapply is the 'Extractor Pattern':

    object Pair {
        val T = new Pair(1,1){
            def unapply(p:(Int, Int)) :Boolean = this._1 == p._1
        }
        def main(args: Array[String]) = {
            (1, 2) match {
                case T() => println("matched")
                case _ => println("not matched")
            }
        } 
    }
    

    You get "matched". Notice the () in the case clause

  2. (1, 1) match { case T => ...

    according to scala spec, section 8.1.5, this is a 'Stable Identifier Pattern', case T matches any value v such that T == v. So we SHOULD get "matched". The "not matched" results are just caused by the bug in current compiler implementation.

xiefei