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

Resuelto Daniel C. Sobral asked hace 15 años • 11 respuestas

Es una triste realidad 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 es un Int, pero no que sea una Lista[ Int], como se puede comprobar 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 -unchecked culpa directamente al 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?!

¿A qué se debe esto y cómo puedo solucionarlo?

Daniel C. Sobral avatar Jul 08 '09 02:07 Daniel C. Sobral
Aceptado

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

Scala se definió con Type Erasure porque la Máquina Virtual Java (JVM), a diferencia de Java, no obtuvo genéricos. Esto significa que, en tiempo de ejecución, sólo 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 característica en Scala que te permite solucionar este problema. Es el Manifiesto . Un Manifiesto es una clase cuyas instancias son objetos que representan tipos. Dado que estas instancias son objetos, puede pasarlas, almacenarlas y, en general, llamar a métodos en ellas. 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" del mismo. Un manifiesto es una clase cuyas instancias representan tipos de Scala. Estos objetos tienen más información que JVM, lo que nos permite probar el tipo parametrizado completo.

Sin embargo, tenga en cuenta que Manifesttodavía es una característica en evolución. Como ejemplo de sus limitaciones, actualmente no sabe nada sobre la varianza y supone que todo es covariante. Espero que se vuelva más estable y sólido una vez que se termine la biblioteca de reflexión de Scala, actualmente en desarrollo.

Daniel C. Sobral avatar Jul 07 '2009 19:07 Daniel C. Sobral

Puedes hacer esto usando TypeTags (como ya menciona Daniel, 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 puedes hacer esto usando ClassTags (lo que te 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!")
}

ClassTags se pueden utilizar siempre y cuando no se espere que el parámetro de tipo Asea un tipo genérico.

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

tksfz avatar Feb 08 '2014 01:02 tksfz

Puedes usar la Typeableclase de tipo de sin forma para obtener el resultado que buscas.

Sesión REPL de muestra,

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 castoperación será un borrado lo más preciso posible dadas las Typeableinstancias disponibles dentro del alcance.

Miles Sabin avatar Jan 11 '2012 15:01 Miles Sabin

Se me ocurrió una solución relativamente simple que sería suficiente en situaciones de uso limitado, esencialmente envolviendo tipos parametrizados que sufrirían el problema de borrado de tipos en clases contenedoras 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 el resultado esperado 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

thricejamie avatar Jun 14 '2011 21:06 thricejamie