Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one completely avoid runtime reflection in Scala?

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

like image 378
bbarker Avatar asked May 02 '18 15:05

bbarker


1 Answers

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.

like image 59
2 revs Avatar answered Sep 17 '22 23:09

2 revs