7

Note this is intended to be a community post, and examples should be added as needed. If you can't directly edit the answer to add examples (either problem examples or solutions), post in a comment with a link to a gist (or similar) or add a separate answer, to be integrated later.

There is the possibility that Scala 3 may not include scala.reflect.runtime at all (Dotty currently doesn't, and plans to do so are not certain). While answers that are applicable to both Scala 2 and Dotty might be preferred for transition purposes and for immediate improvements in performance, Dotty-specific solutions are also welcome.

References

https://www.cakesolutions.net/teamblogs/ways-to-pattern-match-generic-types-in-scala

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
bbarker
  • 11,636
  • 9
  • 38
  • 62
  • 2
    Actually it's vice versa. `TypeTag`s are generated completely at compile-time, but the values can be passed to runtime. They capture information about generic types (and more), that is erased by JVM at runtime. On the other hand, while `ClassTag`s can be generated at compile-time, it's also possible to generate them at runtime. They encapsulate only the information available at runtime, so the generics in them are erased, and that's why the match in your example fails. – Kolmar May 02 '18 at 16:04
  • 1
    From the [Scala doc](https://www.scala-lang.org/api/2.12.5/scala/reflect/ClassTag.html): `ClassTags` are a weaker special case of `TypeTags`, in that they wrap only the runtime class of a given type, whereas a `TypeTag` contains all static type information. That is, `ClassTags` are constructed from knowing only the top-level class of a type, without necessarily knowing all of its argument types. This runtime information is enough for runtime Array creation. – Leo C May 02 '18 at 16:16

1 Answers1

7

General Recommendations

TypeTags are generated at compile time (which may have significant compile time overhead due to macro expansion at each use-site) and employed at runtime, also generating some potential runtime overhead, depending on the exact nature of their use. So even in Scala 2, they should probably only be used a last resort (and we hope to address all such issues here, so that a last resort isn't necessary). By comparison, things that use instanceOf, directly or indirectly, are extremely fast.

Use Java reflection

instanceOf is super fast. classOf (i.e. Java's getClass) is nearly as fast.

Use ClassTag

Referential equality on ClassTags should also be very fast.

Use a wrapped type as an instantiation of a type class

When possible, you may want to consider wrapping your type in a class, to give it a concrete "Java" type. While there will often be overhead, you can possibly use value classes.

Type classes on the wrapped class are then often a good way to expose functionality. As an aside, as @Blaisorblade pointed out, " type tags just a lawless type class (with methods typeName: String and tpe: Type) + instance materialization". Which brings us to the next option:

Uses macros if needed

(currently not supported in Dotty, but planned)

Although perhaps a little harder to get used to, the end result should be cleaner syntax in the code employing the macro than if you were using a TypeTag. Also, macros have uses far beyond TypeTag.

Selected Examples

Matching on a collection's type parameter

Example

A typical use case for TypeTag, taken from the popular post Scala: What is a TypeTag and how do I use it? is to perform ad-hoc polymorphism on a collection type:

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

Unlike ClassTag, TypeTag is runtime reflection. Maybe I'm using it wrong here, though the behavior is quite surprising. At least in a REPL, I don't receive any warnings with the below:

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

meth(List(new Bar))   
res10: String = "list of strings" 

Solution

This is from @smarter on gitter (assumes we don't need to handle empty lists of different types separately):

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

This uses instanceOf, so it should be extremely fast.

Community
  • 1
  • 1
bbarker
  • 11,636
  • 9
  • 38
  • 62