scalatypesscala-2.10reification

What is a TypeTag and how do I use it?


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.


Solution

  • 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] = Foo@681e731c.type#Foo$Bar
    
    scala> val ev2 = m(f2)(b2)
    warning: there were 2 deprecation warnings; re-run with -deprecation for details
    ev2: Manifest[f2.Bar] = Foo@3e50039c.type#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.