384

All I know about TypeTags is that they somehow replaced Manifests. Information on the Internet is scarce and doesn't provide me with a good sense of the subject.

So I'd be happy if someone shared a link to some useful materials on TypeTags including examples and popular use-cases. Detailed answers and explanations are also welcome.

2

1 Answer 1

581
+50

A TypeTag solves the problem that Scala's types are erased at runtime (type erasure). If we wanna do

class Foo
class Bar extends Foo

def meth[A](xs: List[A]) = xs match {
  case _: List[String] => "list of strings"
  case _: List[Foo] => "list of foos"
}

we will get warnings:

<console>:23: warning: non-variable type argument String in type pattern List[String]↩
is unchecked since it is eliminated by erasure
         case _: List[String] => "list of strings"
                 ^
<console>:24: warning: non-variable type argument Foo in type pattern List[Foo]↩
is unchecked since it is eliminated by erasure
         case _: List[Foo] => "list of foos"
                 ^

To solve this problem Manifests were introduced to Scala. But they have the problem not being able to represent a lot of useful types, like path-dependent-types:

scala> class Foo{class Bar}
defined class Foo

scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
warning: there were 2 deprecation warnings; re-run with -deprecation for details
m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]

scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@681e731c
b1: f1.Bar = Foo$Bar@271768ab

scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@3e50039c
b2: f2.Bar = Foo$Bar@771d16b9

scala> val ev1 = m(f1)(b1)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev1: Manifest[f1.Bar] = [email protected]#Foo$Bar

scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = [email protected]#Foo$Bar

scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true

Thus, they are replaced by TypeTags, which are both much simpler to use and well integrated into the new Reflection API. With them we can solve the problem above about path-dependent-types elegantly:

scala> def m(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev
m: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])↩
reflect.runtime.universe.TypeTag[f.Bar]

scala> val ev1 = m(f1)(b1)
ev1: reflect.runtime.universe.TypeTag[f1.Bar] = TypeTag[f1.Bar]

scala> val ev2 = m(f2)(b2)
ev2: reflect.runtime.universe.TypeTag[f2.Bar] = TypeTag[f2.Bar]

scala> ev1 == ev2 // the result is correct, the type tags are different
res30: Boolean = false

scala> ev1.tpe =:= ev2.tpe // this result is correct, too
res31: Boolean = false

They are also easy to use to check type parameters:

import scala.reflect.runtime.universe._

def meth[A : TypeTag](xs: List[A]) = typeOf[A] match {
  case t if t =:= typeOf[String] => "list of strings"
  case t if t <:< typeOf[Foo] => "list of foos"
}

scala> meth(List("string"))
res67: String = list of strings

scala> meth(List(new Bar))
res68: String = list of foos

At this point, it is extremely important to understand to use =:= (type equality) and <:< (subtype relation) for equality checks. Do never use == or !=, unless you absolutely know what you do:

scala> typeOf[List[java.lang.String]] =:= typeOf[List[Predef.String]]
res71: Boolean = true

scala> typeOf[List[java.lang.String]] == typeOf[List[Predef.String]]
res72: Boolean = false

The latter checks for structural equality, which often is not what should be done because it doesn't care about things such as prefixes (like in the example).

A TypeTag is completely compiler-generated, that means that the compiler creates and fills in a TypeTag when one calls a method expecting such a TypeTag. There exist three different forms of tags:

ClassTag substitutes ClassManifest whereas TypeTag is more or less the replacement for Manifest.

The former allows to fully work with generic arrays:

scala> import scala.reflect._
import scala.reflect._

scala> def createArr[A](seq: A*) = Array[A](seq: _*)
<console>:22: error: No ClassTag available for A
       def createArr[A](seq: A*) = Array[A](seq: _*)
                                           ^

scala> def createArr[A : ClassTag](seq: A*) = Array[A](seq: _*)
createArr: [A](seq: A*)(implicit evidence$1: scala.reflect.ClassTag[A])Array[A]

scala> createArr(1,2,3)
res78: Array[Int] = Array(1, 2, 3)

scala> createArr("a","b","c")
res79: Array[String] = Array(a, b, c)

ClassTag provides only the information needed to create types at runtime (which are type erased):

scala> classTag[Int]
res99: scala.reflect.ClassTag[Int] = ClassTag[int]

scala> classTag[Int].runtimeClass
res100: Class[_] = int

scala> classTag[Int].newArray(3)
res101: Array[Int] = Array(0, 0, 0)

scala> classTag[List[Int]]
res104: scala.reflect.ClassTag[List[Int]] =↩
        ClassTag[class scala.collection.immutable.List]

As one can see above, they don't care about type erasure, therefore if one wants "full" types TypeTag should be used:

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

scala> typeTag[List[Int]].tpe
res107: reflect.runtime.universe.Type = scala.List[Int]

scala> typeOf[List[Int]]
res108: reflect.runtime.universe.Type = scala.List[Int]

scala> res107 =:= res108
res109: Boolean = true

As one can see, method tpe of TypeTag results in a full Type, which is the same we get when typeOf is called. Of course, it is possible to use both, ClassTag and TypeTag:

scala> def m[A : ClassTag : TypeTag] = (classTag[A], typeTag[A])
m: [A](implicit evidence$1: scala.reflect.ClassTag[A],↩
       implicit evidence$2: reflect.runtime.universe.TypeTag[A])↩
      (scala.reflect.ClassTag[A], reflect.runtime.universe.TypeTag[A])

scala> m[List[Int]]
res36: (scala.reflect.ClassTag[List[Int]],↩
        reflect.runtime.universe.TypeTag[List[Int]]) =↩
       (scala.collection.immutable.List,TypeTag[scala.List[Int]])

The remaining question now is what is the sense of WeakTypeTag ? In short, TypeTag represents a concrete type (this means it only allows fully instantiated types) whereas WeakTypeTag just allows any type. Most of the time one does not care which is what (which means TypeTag should be used), but for example, when macros are used which should work with generic types they are needed:

object Macro {
  import language.experimental.macros
  import scala.reflect.macros.Context

  def anymacro[A](expr: A): String = macro __anymacro[A]

  def __anymacro[A : c.WeakTypeTag](c: Context)(expr: c.Expr[A]): c.Expr[A] = {
    // to get a Type for A the c.WeakTypeTag context bound must be added
    val aType = implicitly[c.WeakTypeTag[A]].tpe
    ???
  }
}

If one replaces WeakTypeTag with TypeTag an error is thrown:

<console>:17: error: macro implementation has wrong shape:
 required: (c: scala.reflect.macros.Context)(expr: c.Expr[A]): c.Expr[String]
 found   : (c: scala.reflect.macros.Context)(expr: c.Expr[A])(implicit evidence$1: c.TypeTag[A]): c.Expr[A]
macro implementations cannot have implicit parameters other than WeakTypeTag evidences
             def anymacro[A](expr: A): String = macro __anymacro[A]
                                                      ^

For a more detailed explanation about the differences between TypeTag and WeakTypeTag see this question: Scala Macros: “cannot create TypeTag from a type T having unresolved type parameters”

The official documentation site of Scala also contains a guide for Reflection.

12
  • 20
    Thanks for your answer! Some comments: 1) == for types represents structural equality, not reference equality. =:= take into account type equivalences (even non-obvious ones such as equivalences of prefixes that come from different mirrors), 2) Both TypeTag and AbsTypeTag are based on mirrors. The difference is that TypeTag only allows fully instantiated types (i.e. without any type parameters or references abstract type members), 3) A detailed explanation is here: stackoverflow.com/questions/12093752 Sep 2, 2012 at 12:12
  • 10
    4) Manifests have the problem of not being able to represent a lot of useful types. Essentially they can only express type refs (plain types such as Int and generic types such as List[Int]), leaving out such Scala types as e.g. refinements, path-dependent types, existentials, annotated types. Also manifests are a bolt on, so they cannot use the vast knowledge that compiler posesses to, say, calculate a linearization of a type, find out whether one type subtypes another, etc. Sep 2, 2012 at 12:20
  • 9
    5) To the contrast type tags are not "better integrated", they are simply integrated with the new reflection API (unlike manifests which are not integrated with anything). This provides type tags access to certain aspects of the compiler, e.g. to Types.scala (7kloc of code that knows how types are supported to work together), Symbols.scala (3kloc of code that knows how symbol tables work), etc. Sep 2, 2012 at 12:25
  • 9
    6) ClassTag is an exact drop-in replacement for ClassManifest, whereas TypeTag is more or less a substitute for Manifest. More or less, because: 1) type tags don't carry erasures, 2) manifests are a big hack, and we gave up emulating its behavior with type tags. #1 can be fixed by using both ClassTag and TypeTag context bounds when you need both erasures and types, and one usually doesn't care about #2, because it becomes possible to throw away all the hacks and use the full-fledged reflection API instead. Sep 2, 2012 at 12:28
  • 11
    I really hope that the Scala compiler will get rid of deprecated features at some point, to make the set of available features more orthogonal. This is why I like the new macros support because it provides the potential for cleaning up the language, separating some of the features in independent libraries that are not part of the base language. Sep 12, 2012 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.