Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 437
Sergey Weiss Avatar asked Aug 31 '12 15:08

Sergey Weiss


People also ask

What is a ClassTag scala?

A ClassTag[T] stores the erased class of a given type T , accessible via the runtimeClass field. This is particularly useful for instantiating Array s whose element types are unknown at compile time. ClassTag s are a weaker special case of scala. reflect. api.

What is manifest in scala?

A Manifest[T] is an opaque descriptor for type T. Its supported use is to give access to the erasure of the type as a Class instance, as is necessary for the creation of native Arrays if the class is not known at compile time.

What is scala reflect?

Scala reflection enables a form of metaprogramming which makes it possible for programs to modify themselves at compile time. This compile-time reflection is realized in the form of macros, which provide the ability to execute methods that manipulate abstract syntax trees at compile-time.


1 Answers

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:

  • scala.reflect.ClassTag
  • scala.reflect.api.TypeTags#TypeTag
  • scala.reflect.api.TypeTags#WeakTypeTag

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.

like image 55
kiritsuku Avatar answered Oct 08 '22 22:10

kiritsuku