What is the (current) state of scala reflection ca

2019-05-26 03:52发布

问题:

scala appears to be a wonderful addition to the JVM universe. It reminds me of a strange hybrid of C++, C#, and Swift, nested in the JVM world.

However, many of scala's features may be inaccessible due to lacking or out-of-date documentation.

This seems especially true with respect to its reflection capabilities.

For instance, I am assessing whether or not it would be possible to augment scala classes at either runtime or compiletime using scala annotations. I am using the latest scala version 2.11. As a motivating example, let's say I make a case class SimpleAnnotation() extends StaticAnnotation. I would like to, at runtime, find all case classes with that annotation.

This is probably the most typical and vanilla use case for annotations.

In C# and in Java it is relatively straightforward to determine at runtime whether a given class is annotated. This is a canonical sort of use case with a canonical sort of answer. Yet in scala it is unclear to me what I ought to do to achieve this behavior, or even whether it is possible. In particular, after scanning some previous material on scala annotations and reflection, I am left wondering:

  • Is this possible?
  • Is this only possible at runtime or complile time?
  • Is this only possible before or after scala version 2.10?
  • Is this only possible using Java annotations on scala classes?
  • Why does getClass[AnnotatedClass].getAnnotations return such seemingly garbled information?
  • Why are macros and reflection seemingly conflated in scala?

Any guidance is appreciated... and I'm sure I am not the only one who is confused.

回答1:

Reflection and Macros share a lot of the API because they are basically the same thing: Meta Programming. You can generate and execute code, you have to reflect types and so on. There are some differences of course: at compile-time you cannot reflect runtime instances and at runtime you do not get access to internal structure of methods, scope and other information that is deleted during compilation.

Both APIs are still experimental and will probably change in the future in some parts, but they are very usable and also quite well documented. Scala being such a versatile language they are just much more complex than the APIs in Java.

This documentation brings you very far:

http://www.scala-lang.org/api/2.11.7/scala-reflect/

http://www.scala-lang.org/api/2.11.7/scala-compiler/

http://docs.scala-lang.org/overviews/ (Bottom of the page)

This getClass[AnnotatedClass].getAnnotations gives you only Java Annotations, to get Scala Annotations you have to get the Scala Type instead of only the class.

It is possible to access reflections during runtime as well as during compile time, however there are three kinds of annotations:

  1. Plain annotations that are only in the code: These can be accessed from macros in the compilation unit where the macro is called where the macro gets access to the AST

  2. StaticAnnotations that are shared over compilation units: these can be accessed via scala reflection api

  3. ClassfileAnnotations: these represent annotations stored as java annotations. If you want to access them via the Java Reflection API you have to define them in Java though.

Here is an example:

@SerialVersionUID(1) class Blub

Now, we can get the annotation this way:

import scala.reflect.runtime.universe._
val a = typeOf[Blub].typeSymbol.annotations.head

What we actually get is not an instance of an annotation. The runtime environment just gives us what is written in the byte code: the scala code generating the annotation. You can print out the AST that you get:

showRaw(a.tree)

Now, this is already a quite complicated structure, but we can decompose it using pattern matching:

val Apply(_, List(AssignOrNamedArg(_,Literal(Constant(value))))) = a.tree
val uid = value.asInstanceOf[Long]

This is OK for very simple annotations (but we could write those in Java and rely on the JVM creating instances for us). What if we actually want to evaluate that code and generate an instance of the annotation class? (For @SerialVersionUID this would not help us much as the class actually does not give as access to the id...) We can also do that:

case class MyAnnotation(name: String) extends annotation.ClassfileAnnotation

@MyAnnotation(name = "asd")
class MyClass

object MyApp extends App {
  import reflect.runtime.universe._
  import scala.reflect.runtime.currentMirror
  import scala.tools.reflect.ToolBox
  val toolbox = currentMirror.mkToolBox()
  val annotation = typeOf[MyClass].typeSymbol.annotations.head
  val instance =  toolbox.eval(toolbox.untypecheck(annotation.tree))
        .asInstanceOf[MyAnnotation]
  println(instance.name)
}

Note, that this will call the compiler which takes a little time, especially if you do it for the first time. Sophisticated meta programming should really be done at compile time in Scala. A lot of stuff in Java is only done at runtime because you can only have runtime meta programming (Well, there are annotation processors, but they are more limited).