ClassTag, Class and war stories…

Sometimes, when working with Scala, the need of working with type metadata just comes out. Even though macros might seem something too far, theorical and tricky (we’ll speak about them later), sometimes getting some info about runtime classes is more than enough.

ClassTag

You could see a ClassTag as some ‘wrapper’ that Scala adds over java.lang.Class runtime classes.

In order to work with ClassTag we need to import:

import scala.reflect.{ClassTag,classTag}

Whereas the first member imported is the class, the second one is just a method that allows to get implicitly the ClassTag of a certain type. I.e.:

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

Actually, it’s just syntax sugar for:

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

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

There are methods (currently deprecated by type checking via TypeTag) like <:< or =:= that allow checking at compile time the type bounds.
For example, this:

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

allows constraining the relation between T and U types:

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)

Where are my type parameters?

One of the main problems working with ClassTag is the information loss about the parameter types that you class might have. For example, if we ask for the ClassTag of List[Int] we’ll get that:

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

As you can see, information about Int that parameterizes the list is lost. This process that removes parameter types info at runtime is well known as type erasure.

62517760

Nevertheless, if we need info about the static type, we can use TypeTags (which should be properly explained at future posts, when talking about 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]]

Frequent problems

How to get the Class of a known type T?

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

I have a Class, but I need a ClassTag

As easy as:

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

Notice that, unless we explicitly point the type, ClassTag builder will infer the most generic possible type (Nothing).

Is T a subtype of U?

We’ve seen before that, at compile time, we can use <:< and so for constraining type bounds but, what if it takes place at runtime? How can we check it?

We can use isAssignableFrom method, from class Class that indicates whether a class is the same class (or superclass) compared to another one:

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

We could try this code snippet with:

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

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

Infer the type of an Any

This tip comes out of a question that a colleague made me. ‘Is there any way to, having the folling function:

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

check if t value is T typed?’
First obvious step for checking it would be something similar to:

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

Compiler is warning us (Forewarned is forearmed) that we won’t have information about T at runtime, that it’s getting lost. What if we try with different types? It eats them all:

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

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

In this case, we said ‘okay, what if we just try to check if classes are assignables, getting the info from the ClassTag‘?
It happened that,

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

The x class is java.lang.Integer, which is not assignable from Int class (the same happens with many other primitive types).

So we finally…

…realized that ClassTag has a wonderfull unapply method that allows to get a typed value if this belongs to the ClassTag parameterized class. Just what we needed:

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

When we tried it at the REPL, we could gladly check that:

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

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

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

Conclusions

It’s not very common to deal with all these isses unless you’re building a generic enough system. However, it doesn’t harm you to have a little idea about what the dark side is about…

137134463824621

Peace out!

Anuncios

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!