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

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