ClassTag, Class y batallitas de la mili…

En ocasiones, al trabajar en Scala, surge la necesidad de trabajar con meta-información sobre los tipos. Si bien las macros pueden parecer algo lejano y demasiado tedioso (ya hablaremos de ellas), muchas veces nos basta con poder obtener algo de información sobre las clases de runtime.

ClassTag

Se trata de un ‘envoltorio’ que añade Scala sobre las clases de runtime de java.lang.Class.

Para trabajar con ClassTag debemos importar:

import scala.reflect.{ClassTag,classTag}

Mientras que el primer valor que se importa es la clase, el segundo es un método para obtener implícitamente el ClassTag de un cierto tipo. Por ejemplo:

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

En realidad no es más que syntax sugar para lo siguiente:

def classTag[T:ClassTag]: ClassTag[T] = 
  implicitly[ClassTag[T]]

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

Disponen de métodos (ahora deprecados por la verificación de tipos mediante TypeTag) como <:< ó =:= que permiten comprobar en tiempo de compilación los bounds de un tipo.
Por ejemplo:

def myMethod[T,U](t: T,u: U)(implicit ev: <:<[T,U]):Unit = 
  println(t,u)

permite restringir la relación entre los tipos T y U:

scala> myMethod(new Animal,new Car)
<console>:11: error: Cannot prove that Animal <:< Car. myMethod(new Animal,new Car) ^ scala> myMethod(new Cat,new Animal)
($line15.$read$$iw$$iw$Cat@350aac89,$line14.$read$$iw$$iw$Animal@1c5920df)

¿Y mis tipos?

Uno de los problemas al trabajar con ClassTag es la pérdida de información sobre los tipos que pueden parametrizar tu clase. Por ejemplo, si preguntamos por el ClassTag de List[Int] obtendremos que:

scala> println(scala.reflect.classTag[List[Int]])
scala.collection.immutable.List

Como podéis ver, se pierde la información sobre el tipo Int que parametriza la lista. Este proceso de borrado de información de tipos en runtime es conocido como type erasure.

62517760

No obstante, en caso de necesitar información estática sobre el tipo, podemos hacer uso de los TypeTag (los cuales introduciremos en futuros posts, cuando hablemos de macros).

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> val tt = typeTag[List[Int]]
tt: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

Problemas frecuentes

¿Cómo obtener la Class de un tipo T?

scala> classOf[Int]
res0: Class[Int] = int

Tengo un Class, pero necesito un ClassTag

Tan sencillo como

scala> val clazz = classOf[Int]
clazz: Class[Int] = int

scala> val ctag = ClassTag(clazz)
ctag: scala.reflect.ClassTag[Nothing] = Int

scala> val ctag = scala.reflect.ClassTag[Int](clazz)
ctag: scala.reflect.ClassTag[Int] = Int

Fijaos que salvo que indiquemos el tipo de manera explícita, el constructor de ClassTag inferirá el tipo más genérico posible (en este caso Nothing).

¿T es un subtipo de U?

Hemos visto antes, que en tiempo de compilación, podemos usar <:< y similares para restringir los tipos pero, ¿qué ocurre en tiempo de runtime?

Podemos hacer uso del método isAssignableFrom de la case Class que nos indica si una clase es la misma (o superclase) comparado con otra:

object A{
  def apply[T:ClassTag] = new {
    def isA[U:ClassTag]: Boolean = 
      classTag[T].runtimeClass.isAssignableFrom(classTag[U].runtimeClass)
  }
}

Podríamos probar este snippet con:

scala> A[Int].isA[String]
res0: Boolean = false

scala> A[String].isA[String]
res1: Boolean = true

Detectar el tipo a partir de un Any

Este tip surge por una duda que me planteó un compañero acerca de si existía una manera de, teniendo la siguiente función:

def isA[T](t: Any): Boolean = ???

comprobar si el valor t era de tipo T.
El primer paso obvio para comprobarlo sería algo como:

scala> def isA[T](t: Any): Boolean = t.isInstanceOf[T]
<console>:7: warning: abstract type T is unchecked since it is eliminated by erasure
       def isA[T](t: Any): Boolean = t.isInstanceOf[T]
                                                   ^
isA: [T](t: Any)Boolean

El compilador está avisando (y el que avisa no es traidor) que no tenemos información sobre T en tiempo de runtime, que se está perdiendo. ¿Qué ocurre si le pasamos distintos tipos? Se los zampa:

scala> isA[Int]("")
res2: Boolean = true

scala> isA[Int](3)
res3: Boolean = true

En este caso dijimos “bueno, ¿porque no tratamos de comprobar si las clases son asignables entre sí, obteniendo información del ClassTag“?
Pues ocurría que,

scala> val x: Any = 3
x: Any = 3

scala> x.getClass
res2: Class[_] = class java.lang.Integer

scala> classOf[Int].isAssignableFrom(classOf[Int])
res3: Boolean = true

scala> classOf[Int].isAssignableFrom(classOf[java.lang.Integer])
res4: Boolean = false

La clase de x es java.lang.Integer, que no es asignable por parte de Int (lo mismo ocurría con variedad de tipos primitivos).

Al final…

Finalmente nos dimos cuenta de que ClassTag tiene un maravilloso método unapply que permite extraer un valor tipado si este pertenece a la clase del ClassTag. Justo lo que necesitábamos:

def isA[T:ClassTag](t: Any): Boolean = 
  classTag[T].unapply(t).isDefined

al probarlo en la REPL comprobamos con alegría que:

scala> val x: Any = 3
x: Any = 3

scala> isA[String](x)
res5: Boolean = false

scala> isA[Int](x)
res6: Boolean = true

Conclusiones

No es habitual tener que lidiar con estos temas salvo que se estén construyendo sistemas lo suficientemente genéricos. No obstante, nunca está de más echar un vistazo al lado oscuro…

137134463824621

¡Agur de limón!

Anuncios

3 thoughts on “ClassTag, Class y batallitas de la mili…

  1. hola!

    tengo una duda, que hace este trozo?

    object A{
    def apply[T:ClassTag] = new {
    def isA[U:ClassTag]: Boolean =
    classTag[T].runtimeClass.isAssignableFrom(classTag[U].runtimeClass)
    }
    }

    Me pierde lo del new {…..}

    Es que no se como buscar ahora mismo, perdón por la pregunta, que esta un poco fuera de contexto 🙂

    Tenéis algún post sobre los implicits?

    un saludo

    Le gusta a 1 persona

    • Jajajaja no te fijes mucho tampoco en esa parte.

      El ‘new {…}’ crea una instancia de una clase anónima con el body que definas dentro. Es solo syntax sugar para luego poder invocar a ‘A[T].isA[U]’

      Creo que haremos un post entonces para hablar de mecanismos para crear DSLs en Scala.

      Gracias por tu feedback 🙂

      Le gusta a 1 persona

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s