reflection

TypeTags and Manifests

As with other JVM languages, Scala’s types are erased at compile time. This means that if you were to inspect the runtime type of some instance, you might not have access to all type information that the Scala compiler has available at compile time.

Like scala.reflect.Manifest, TypeTags can be thought of as objects which carry along all type information available at compile time, to runtime. For example, TypeTag[T] encapsulates the runtime type representation of some compile-time type T. Note however, that TypeTags should be considered to be a richer replacement of the pre-2.10 notion of a Manifest, that are additionally fully integrated with Scala reflection.

There exist three different types of TypeTags:

  1. scala.reflect.api.TypeTags#TypeTag. A full type descriptor of a Scala type. For example, a TypeTag[List[String]] contains all type information, in this case, of type scala.List[String].

  2. scala.reflect.ClassTag. A partial type descriptor of a Scala type. For example, a ClassTag[List[String]] contains only the erased class type information, in this case, of type scala.collection.immutable.List. ClassTags provide access only to the runtime class of a type. Analogous to scala.reflect.ClassManifest.

  3. scala.reflect.api.TypeTags#WeakTypeTag. A type descriptor for abstract types (see corresponding subsection below).

Obtaining a TypeTag

Like Manifests, TypeTags are always generated by the compiler, and can be obtained in three ways.

via the Methods typeTag, classTag, or weakTypeTag

One can directly obtain a TypeTag for a specific type by simply using method typeTag, available through Universe.

For example, to obtain a TypeTag which represents Int, we can do:

import scala.reflect.runtime.universe._
val tt = typeTag[Int]

Or likewise, to obtain a ClassTag which represents String, we can do:

import scala.reflect._
val ct = classTag[String]

Each of these methods constructs a TypeTag[T] or ClassTag[T] for the given type argument T.

Using an Implicit Parameter of Type TypeTag[T], ClassTag[T], or WeakTypeTag[T]

As with Manifests, one can in effect request that the compiler generate a TypeTag. This is done by simply specifying an implicit evidence parameter of type TypeTag[T]. If the compiler fails to find a matching implicit value during implicit search, it will automatically generate a TypeTag[T].

Note: this is typically achieved by using an implicit parameter on methods and classes only.

For example, we can write a method which takes some arbitrary object, and using a TypeTag, prints information about that object’s type arguments:

import scala.reflect.runtime.universe._

def paramInfo[T](x: T)(implicit tag: TypeTag[T]): Unit = {
  val targs = tag.tpe match { case TypeRef(_, _, args) => args }
  println(s"type of $x has type arguments $targs")
}

Here, we write a generic method paramInfo parameterized on T, and we supply an implicit parameter (implicit tag: TypeTag[T]). We can then directly access the type (of type Type) that tag represents using method tpe of TypeTag.

We can then use our method paramInfo as follows:

scala> paramInfo(42)
type of 42 has type arguments List()

scala> paramInfo(List(1, 2))
type of List(1, 2) has type arguments List(Int)

Using a Context bound of a Type Parameter

A less verbose way to achieve exactly the same as above is by using a context bound on a type parameter. Instead of providing a separate implicit parameter, one can simply include the TypeTag in the type parameter list as follows:

def myMethod[T: TypeTag] = ...

Given context bound [T: TypeTag], the compiler will simply generate an implicit parameter of type TypeTag[T] and will rewrite the method to look like the example with the implicit parameter in the previous section.

The above example rewritten to use context bounds is as follows:

import scala.reflect.runtime.universe._

def paramInfo[T: TypeTag](x: T): Unit = {
  val targs = typeOf[T] match { case TypeRef(_, _, args) => args }
  println(s"type of $x has type arguments $targs")
}

scala> paramInfo(42)
type of 42 has type arguments List()

scala> paramInfo(List(1, 2))
type of List(1, 2) has type arguments List(Int)

WeakTypeTags

WeakTypeTag[T] generalizes TypeTag[T]. Unlike a regular TypeTag, components of its type representation can be references to type parameters or abstract types. However, WeakTypeTag[T] tries to be as concrete as possible, i.e., if type tags are available for the referenced type arguments or abstract types, they are used to embed the concrete types into the WeakTypeTag[T].

Continuing the example above:

def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = {
  val targs = tag.tpe match { case TypeRef(_, _, args) => args }
  println(s"type of $x has type arguments $targs")
}

scala> def foo[T] = weakParamInfo(List[T]())
foo: [T]=> Unit

scala> foo[Int]
type of List() has type arguments List(T)

TypeTags and Manifests

TypeTags correspond loosely to the pre-2.10 notion of scala.reflect.Manifests. While scala.reflect.ClassTag corresponds to scala.reflect.ClassManifest and scala.reflect.api.TypeTags#TypeTag mostly corresponds to scala.reflect.Manifest, other pre-2.10 Manifest types do not have a direct correspondence with a 2.10 ”Tag” type.

  • scala.reflect.OptManifest is not supported. This is because Tags can reify arbitrary types, so they are always available.

  • There is no equivalent for scala.reflect.AnyValManifest. Instead, one can compare their Tag with one of the base Tags (defined in the corresponding companion objects) in order to find out whether or not it represents a primitive value class. Additionally, it’s possible to simply use <tag>.tpe.typeSymbol.isPrimitiveValueClass.

  • There are no replacement for factory methods defined in the Manifest companion objects. Instead, one could generate corresponding types using the reflection APIs provided by Java (for classes) and Scala (for types).

  • Certain manifest operations(i.e., <:<, >:> and typeArguments) are not supported. Instead, one could use the reflection APIs provided by Java (for classes) and Scala (for types).

In Scala 2.10, scala.reflect.ClassManifests are deprecated, and it is planned to deprecate scala.reflect.Manifest in favor of TypeTags and ClassTags in an upcoming point release. Thus, it is advisable to migrate any Manifest-based APIs to use Tags.

blog comments powered by Disqus