scalapolymorphismtype-erasureclasstag

Scala Any => native format


I have a map of type Map[_, Any], and I want to extract the values in their native format (without resorting to .asInstanceOf[_]).

Something like this...

val m: Map[String, Any] = Map("i" -> 1, "s" -> "test")

val i: Option[Int] = m.get("i")
val s: Option[String] = m.get("s")

Obviously that fails.

I don't like this approach, but I was thinking I could do something like this... but even this still comes out as Any instead of Int or String.

trait MyType[A] {
  def value: A
}

implicit class MyInt(i: Int) extends MyType[Int] { def value: Int = i }
implicit class MyString(s: String) extends MyType[String] { def value: String = s }

val m: Map[String, MyType[_]] = Map("i" -> 1, "s" -> "test")

val i: Option[Int] = m.get("i").map(_.value)
val s: Option[String] = m.get("s").map(_.value)

Then I thought maybe some wrapper around the Map...

case class MyMap(m: Map[String, Any]) {
  def get[A](k: String)(implicit ev: Option[Any] => Option[A]): Option[A] = m.get(k)
}

But that STILL comes out as Any. I just can't figure out how to convert Any => native.

So my questions are...

  1. Why does this fail?
  2. What are better way(s) to get the values out in their native format? Simplest and/or no external dependencies would be ideal... but I'm honestly open to anything (though caveat I'm still on scala 2.11 for now).

Thanks!


Solution

  • You cannot guess the runtime type for the reasons that have already been explained in the comments - this information is not there, once it's Any, all type information is lost, there is nothing you can do about it.

    So, you'll have to provide the expected type yourself. How about an .as[T] helper method?

    // This code is specifically for 2.11, please don't use it for more recent versions,
    // see link below.
    
    val m: Map[String, Any] = Map("i" -> 1, "s" -> "test")
    
    import scala.reflect.{ ClassTag, classTag }
    
    implicit class As(a: Any) {
      def as[T](implicit ct: ClassTag[T]): Option[T] = ct.unapply(a)
    }
    
    println(m("i").as[Int].map(_ + 41).get)
    println(m("s").as[String].map("This is a " + _).get)
    

    This will print

    42
    This is a test
    

    Brief explanation:

    It will not work for generic types like List[Int] vs. List[String] etc, because this information is simply not available at runtime.

    EDIT: Thanks @MarioGalic for greatly simplifying the solution.