Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala type erasure in pattern matching Map[String, Int]

In Scala 2.10, the compiler warns the given code:

  private def getStrFromOpt[T](opt: Option[T]): String = opt match {
    case Some(s: String) => s
    case Some(i: Int) => i.toString()
    case Some(l: Long) => l.toString()
    case Some(m: Map[String, Int]) => m map ({ case (k, v) =>
      "(" + k + ", " + v + ")" }) mkString ("(", ", ", ")")
    case _ => ""
  }

with the message non-variable type argument String in type pattern Map[String,Int] is unchecked since it is eliminated by erasure: case Some(m: Map[String, Int]) ....

How can I get rid of this warning? What if I will have a Map[String, MyObj] which I want to include as a case in this matching - how can I distinguish the two cases with parameterised Maps?

like image 755
Colon Peters Avatar asked Jul 17 '15 07:07

Colon Peters


1 Answers

you can use Scala annotation @unchecked to suppress the warning, for your second question, I suggest you use Scala reflection -- TypeTag. I am using Scala 2.11.4, here is the sample code, FYI.

import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.universe.{ typeTag, TypeTag }

object MapType extends App {
  def getTypeTag[T: TypeTag](t: T) = typeTag[T].tpe
  def getTypeTag[T: TypeTag] = ru.typeOf[T]

  // context bound T: ru.TypeTag cause typeOf 
  // requires implicit parameter
  def getStrFromOpt[T: TypeTag](opt: Option[T]): String = {
    opt match {
       case Some(s: String) => s
       case Some(i: Int) => i.toString()
       case Some(l: Long) => l.toString()
       case Some(m: Map[String, Int] @ unchecked) 
         if getTypeTag[T] =:= getTypeTag[Map[String, Int]] => "Int Map"
       case Some(m: Map[String, String] @ unchecked) 
         if getTypeTag[T] =:= getTypeTag[Map[String, String]] => "String Map"
       case _ => ""
     }
  }

  // "Int Map"
  println(getStrFromOpt(Some(Map("a" -> 2, "b" -> 3)))) 
  // "String Map"
  println(getStrFromOpt(Some(Map("a" -> "2", "b" -> "3")))) 
}

Actually, Scala uses the erasure model of generics just like Java does. So no information about type arguments is maintained at runtime.

def isIntMap(x: Any) = x match {
   case m: Map[Int, Int] => true
   case _ => false
}

For the code above, Scala compiler can not decide whether m is Map[Int, Int] or not. Therefore, isIntMap(Map("a"-> "b")) return true, which seems to be non-intuitive. To alert you the runtime behavior, Scala compiler emitted that un-checked message.

However, Array is an Exception, its element type is stored with element value.

def isIntArray(x: Any) = x match {
  case a: Array[String] => "yes"
  case _ => "no"
} 

scala> isIntArray(Array(3))
res1: String = no

scala> isIntArray(Array("1"))
res2: String = yes
like image 167
Allen Chou Avatar answered Oct 20 '22 04:10

Allen Chou