scala type-erasure

¿Cómo puedo evitar el borrado de tipos en Scala? O, ¿por qué no puedo obtener el parámetro de tipo de mis colecciones?



type-erasure (11)

Es un triste hecho de la vida en Scala que si crea una instancia de una Lista [Int], puede verificar que su instancia es una Lista, y puede verificar que cualquier elemento individual de ella sea un Int, pero no que sea una Lista [ Int], como se puede verificar fácilmente:

scala> List(1,2,3) match { | case l : List[String] => println("A list of strings?!") | case _ => println("Ok") | } warning: there were unchecked warnings; re-run with -unchecked for details A list of strings?!

La opción "Checkecked" pone la culpa directamente en el borrado de tipos:

scala> List(1,2,3) match { | case l : List[String] => println("A list of strings?!") | case _ => println("Ok") | } <console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure case l : List[String] => println("A list of strings?!") ^ A list of strings?!

¿Por qué es eso, y cómo me las arreglo?


Esta respuesta utiliza el Manifest -API, que está en desuso a partir de Scala 2.10. Por favor, vea las respuestas a continuación para obtener más soluciones actuales.

Scala se definió con Borrado de Tipo porque la Máquina Virtual de Java (JVM), a diferencia de Java, no tenía genéricos. Esto significa que, en tiempo de ejecución, solo existe la clase, no sus parámetros de tipo. En el ejemplo, JVM sabe que está manejando un scala.collection.immutable.List , pero no que esta lista esté parametrizada con Int .

Afortunadamente, hay una función en Scala que te permite evitar eso. Es el Manifiesto . Un manifiesto es una clase cuyas instancias son objetos que representan tipos. Dado que estas instancias son objetos, puede pasarlos, almacenarlos y, en general, llamar a métodos. Con el soporte de parámetros implícitos, se convierte en una herramienta muy poderosa. Tomemos el siguiente ejemplo, por ejemplo:

object Registry { import scala.reflect.Manifest private var map= Map.empty[Any,(Manifest[_], Any)] def register[T](name: Any, item: T)(implicit m: Manifest[T]) { map = map.updated(name, m -> item) } def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = { map get key flatMap { case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None } } } scala> Registry.register("a", List(1,2,3)) scala> Registry.get[List[Int]]("a") res6: Option[List[Int]] = Some(List(1, 2, 3)) scala> Registry.get[List[String]]("a") res7: Option[List[String]] = None

Al almacenar un elemento, también almacenamos un "Manifiesto" de él. Un manifiesto es una clase cuyas instancias representan tipos de Scala. Estos objetos tienen más información que la JVM, lo que nos permite probar el tipo completo y parametrizado.

Tenga en cuenta, sin embargo, que un Manifest sigue siendo una característica en evolución. Como ejemplo de sus limitaciones, actualmente no sabe nada acerca de la varianza y asume que todo es co-variante. Espero que se vuelva más estable y sólido una vez que la biblioteca de reflexión de Scala, actualmente en desarrollo, se complete.


Como Java no conoce el tipo de elemento real, me pareció más útil simplemente usar la List[_] . Luego, la advertencia desaparece y el código describe la realidad: es una lista de algo desconocido.


Encontré una solución un poco mejor para esta limitación del lenguaje por lo demás impresionante.

En Scala, el problema de la eliminación de tipos no se produce con matrices. Creo que es más fácil demostrar esto con un ejemplo.

Digamos que tenemos una lista de (Int, String) , luego lo siguiente muestra una advertencia de borrado de tipo

x match { case l:List[(Int, String)] => ... }

Para solucionar esto, primero cree una clase de caso:

case class IntString(i:Int, s:String)

Luego, en el patrón de coincidencia, haga algo como:

x match { case a:Array[IntString] => ... }

Lo que parece funcionar perfectamente.

Esto requerirá cambios menores en su código para trabajar con matrices en lugar de listas, pero no debería ser un problema importante.

Tenga en cuenta que al usar el case a:Array[(Int, String)] aún aparecerá una advertencia de borrado de tipo, por lo que es necesario usar una nueva clase de contenedor (en este ejemplo, IntString ).



Me pregunto si esto es una solución adecuada:

scala> List(1,2,3) match { | case List(_: String, _*) => println("A list of strings?!") | case _ => println("Ok") | }

No coincide con el caso de la "lista vacía", pero da un error de compilación, ¡no una advertencia!

error: type mismatch; found: String requirerd: Int

Esto por otro lado parece funcionar ....

scala> List(1,2,3) match { | case List(_: Int, _*) => println("A list of ints") | case _ => println("Ok") | }

¿No es un poco mejor o me estoy perdiendo el punto aquí?



Puede usar la clase de tipos Typeable para obtener el resultado que está buscando,

Muestra de sesión de REPL,

scala> import shapeless.syntax.typeable._ import shapeless.syntax.typeable._ scala> val l1 : Any = List(1,2,3) l1: Any = List(1, 2, 3) scala> l1.cast[List[String]] res0: Option[List[String]] = None scala> l1.cast[List[Int]] res1: Option[List[Int]] = Some(List(1, 2, 3))

La operación de Typeable será lo más precisa posible en el borrado de Typeable instancias disponibles en Typeable dentro del alcance.


Puedes hacer esto usando TypeTags (como Daniel ya mencionó, pero lo explicaré explícitamente):

import scala.reflect.runtime.universe._ def matchList[A: TypeTag](list: List[A]) = list match { case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!") case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!") }

También puede hacer esto usando ClassTags (lo que le evita tener que depender de scala-reflect):

import scala.reflect.{ClassTag, classTag} def matchList2[A : ClassTag](list: List[A]) = list match { case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!") case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!") }

Las ClassTags pueden usarse siempre que no espere que el parámetro de tipo A sea ​​un tipo genérico.

Desafortunadamente, es un poco detallado y necesita la anotación @unchecked para suprimir una advertencia del compilador. En el futuro, el compilador puede incorporar automáticamente TypeTag a la coincidencia de patrones: https://issues.scala-lang.org/browse/SI-6517


Quería agregar una respuesta que generalice el problema a: ¿Cómo se obtiene una representación de cadena del tipo de mi lista en tiempo de ejecución?

import scala.reflect.runtime.universe._ def whatListAmI[A : TypeTag](list : List[A]) = { if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type println("its a String") else if (typeTag[A] == typeTag[Int]) println("its a Int") s"A List of ${typeTag[A].tpe.toString}" } val listInt = List(1,2,3) val listString = List("a", "b", "c") println(whatListAmI(listInt)) println(whatListAmI(listString))


Se me ocurrió una solución relativamente simple que sería suficiente en situaciones de uso limitado, esencialmente ajustando tipos parametrizados que sufrirían el problema de borrado de tipo en las clases de envoltura que se pueden usar en una declaración de coincidencia.

case class StringListHolder(list:List[String]) StringListHolder(List("str1","str2")) match { case holder: StringListHolder => holder.list foreach println }

Esto tiene la salida esperada y limita el contenido de nuestra clase de caso al tipo deseado, Listas de cadenas.

Más detalles aquí: http://www.scalafied.com/?p=60


Usando patrón de protección de partido

list match { case x:List if x.isInstanceOf(List[String]) => do sth case x:List if x.isInstanceOf(List[Int]) => do sth else }