1

Am reporting a feature that sounds wierd and unable to reason the following behavior with Pattern matching in scala.

def typesPattern(x:Any)= x match{
    case s:String⇒ s.length
    case n:Map[Int,Int]⇒println("Map[Int,Int]");var a =  n.iterator.next();println(a._1);println(a._2);n.size;      
    case n:Map[a,b]⇒println("Map[a,b]");n.size;     
    case m:Map[_,_]⇒ m.size
    case _ ⇒ -1

    }
}

When i invoke above function with following println(typesPattern(Map("a"→10))) i get following error Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101) at scala.Tuple2._1$mcI$sp(Tuple2.scala:20)

First question that I have is "WHY MAP[String->Int] is getting matched with MAP[INT,INT]?", it should have rather matched with MAP[_,_].

Interestingly when I edit the pattern matching code and take out the code that extracts tuple from Map and prints key and value pair

`def typesPattern(x:Any)= x match{
        case s:String⇒ s.length
        case n:Map[Int,Int]⇒println("Map[Int,Int]");n.size;     
        case n:Map[a,b]⇒println("Map[a,b]");n.size;     
        case m:Map[_,_]⇒ m.size
        case _ ⇒ -1

        }
    }`

Now the same invocation like earlier println(typesPattern(Map("a"→10))) does match MAP[INT,INT] without issues and prints the size.

Map[Int,Int] 
    1

Second question "WHY THIS TIME SCALA IS ABLE TO MATCH MAP[String->INT] with MAP[INT->INT] (which i still wonder how?) without issues?

Gurupraveen
  • 181
  • 1
  • 13

4 Answers4

2

You maybe tried to look at warnings compiler gives you?

<console>:12: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
       case n:Map[Int,Int]⇒println("Map[Int,Int]");var a =  n.iterator.next();println(a._1);println(a._2);n.size;
              ^
<console>:13: warning: unreachable code
       case n:Map[a,b]⇒println("Map[a,b]");n.size;

Actually, both of these lines:

    case n:Map[a,b]⇒println("Map[a,b]");n.size;     
    case m:Map[_,_]⇒ m.size

are unreachable, because all three lines that match on map are equivalent, at least their pattern will match the same things.

In runtime there is no generic types, they are erased, so Map[A, B] is just a Map. So your only case matching the map is the first one, as they are tested in order

case n:Map[Int,Int]⇒println("Map[Int,Int]");var a =  n.iterator.next();println(a._1);println(a._2);n.size;      

You get a ClassCastException only when you try to use the values treating them like an Int, because they get cast only if you try to use them. Checking size does not depend on type of its values.

Łukasz
  • 8,555
  • 2
  • 28
  • 51
1

This problem happens because of generics type erasure. In runtime there is no difference between Map of any types. That's why pattern matches at first suitable case.

Simple snippet to check it:

List[String]().isInstanceOf[List[String]]    // true
List[String]().isInstanceOf[List[Integer]]   // true
Sergii Lagutin
  • 10,561
  • 1
  • 34
  • 43
0

It's because of the type erasure. Usage of generic types is of no use in case clauses as it does not retain type information. So MAP[String->Int] is equivalent to Map. That is why MAP[String->Int] match with MAP[Int->Int].

justAbit
  • 4,226
  • 2
  • 19
  • 34
0

Wouldn't much easier if instead of trying to use pattern matching, you use instead implicits and type classes mechanism?

trait TypePattern[A,B] {
  def pattern(a: A):B
}

implicit object stringPattern extends TypePattern[String,Int] {
  override def pattern(a: String): Int = a.length
}

implicit object mapIntIntPattern extends TypePattern[Map[Int, Int],Int] {
  override def pattern(n: Map[Int, Int]): Int = {
    println("Map[Int,Int]")
    var a =  n.iterator.next()
    println(a._1)
    println(a._2)
    n.size
  }
}

implicit object mapAnyPattern extends TypePattern[Map[Any, Any],Int] {
  override def pattern(a: Map[Any, Any]): Int = {
    println("Map[a,b]")
    a.size
  }
}

def pattern[A,B](x: A)(implicit typePattern: TypePattern[A,B]): B = {
  typePattern.pattern(x)
}