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.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Sergey Weiss
  • 5,944
  • 8
  • 31
  • 40
  • 2
    The following article from the Scala documentation describes both the what and the why of type tags, as well as how to use them in your code: http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html – btiernay Jan 04 '15 at 01:04
  • https://stackoverflow.com/questions/59473734 https://stackoverflow.com/a/74063258/5249621 https://stackoverflow.com/a/74377253/5249621 https://stackoverflow.com/questions/73836319 – Dmytro Mitin Apr 08 '23 at 08:20

1 Answers1

581

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.

Community
  • 1
  • 1
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • 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: http://stackoverflow.com/questions/12093752 – Eugene Burmako Sep 02 '12 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. – Eugene Burmako Sep 02 '12 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. – Eugene Burmako Sep 02 '12 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. – Eugene Burmako Sep 02 '12 at 12:28
  • 3
    @EugeneBurmako: Thanks for the useful comments. I added some of the information provided, but didn't care about information how things are done internally. If you see them important enough for suggestion it would be nice if you can write another answer. – kiritsuku Sep 02 '12 at 14:55
  • 3
    Looks great! One more suggestion. Please, adjust the info about the difference between AbsTypeTag and TypeTag. "In short, TypeTag represents a concrete type ... whereas AbsTypeTag an abstract one". The "abstract one" sounds ambiguous. I'd say just "allows any type". – Eugene Burmako Sep 02 '12 at 19:29
  • 3
    Also in your macro example "if one want to use A here AbsTypeTag is needed to get the correct type". I'd say just "to get a Type for A add the c.AbsTypeTag context bound and use implicitly[c.AbsTypeTag[A]].tpe". That's because strictly speaking, one can get the type for A directly from c.macroApplication, but that'd be much less convenient. – Eugene Burmako Sep 02 '12 at 19:31
  • @EugeneBurmako: Ok, I just made the changes. – kiritsuku Sep 02 '12 at 21:36
  • 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. – Alexandru Nedelcu Sep 12 '12 at 09:35
  • Just another question confused me so much: Below function take the TypeTag of generic type A as an implicit parameter and pass it to the function. def meth[A : TypeTag](xs: List[A]) = ??? <=> def meth[A](xs: List[A])(implicit tag :TypeTag[A]) = ??? How did jvm extract the TypeTag information at runtime? Or for short, how the implicit parameter is generated? – Machi Sep 02 '21 at 09:38
  • 1
    @Machi The JVM is not involved. scalac generates the TypeTag and adds it to the call of the function. It is stored in the bytecode and the JVM just have to load it. – kiritsuku Sep 02 '21 at 13:35
  • a `TypeTag` is a thing which is obsolete with `Scala 3` – Hartmut Pfarr Jul 21 '22 at 17:03