scala представляется прекрасным дополнением к юниверсу JVM. Это напоминает мне странный гибрид С++, С# и Swift, вложенный в мир JVM.
Однако многие функции scala могут быть недоступны из-за отсутствия или устаревшей документации.
Это особенно верно в отношении возможностей отражения.
Например, я оцениваю, можно ли расширить классы scala во время выполнения или компиляции с помощью аннотаций scala. Я использую последнюю версию scala версии 2.11. В качестве мотивирующего примера предположим, что я делаю case class SimpleAnnotation() extends StaticAnnotation
. Во время выполнения я хотел бы найти все case class
es с этой аннотацией.
Это, вероятно, самый типичный и ванильный вариант использования аннотаций.
В С# и в Java относительно просто определить во время выполнения, является ли данный класс аннотированным. Это канонический вид прецедента с каноническим ответом. Тем не менее, в scala мне непонятно, что я должен сделать для достижения такого поведения или даже того, возможно ли это. В частности, после сканирования какого-либо предыдущего материала по аннотации и отражению scala, мне остается задаться вопросом:
getClass[AnnotatedClass].getAnnotations
возвращает такую, казалось бы, искаженную информацию?Любое руководство ценится... и я уверен, что я не единственный, кто смущен.
Reflection и Macros используют много API, потому что они в основном одно и то же: Meta Programming. Вы можете создавать и выполнять код, вы должны отражать типы и т.д. Конечно, есть некоторые отличия: во время компиляции вы не можете отображать экземпляры среды выполнения и во время выполнения вы не получаете доступ к внутренней структуре методов, области и другой информации, которая удаляется во время компиляции.
Оба API все еще являются экспериментальными и, вероятно, будут изменяться в будущем в некоторых частях, но они очень полезны, а также достаточно хорошо документированы. Scala, являясь таким универсальным языком, они намного сложнее, чем API в Java.
В этой документации вы очень далеко:
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/ (внизу страницы)
Этот getClass[AnnotatedClass].getAnnotations
дает вам только аннотации Java, чтобы получить Scala Аннотации, вам нужно получить тип Scala вместо класса.
Доступ к отражениям возможен во время выполнения, а также во время компиляции, однако есть три вида аннотаций:
Простые аннотации, которые находятся только в коде: к ним можно получить доступ из макросов в блоке компиляции, где макрос вызывается там, где макрос получает доступ к AST
StaticAnnotations, которые разделяются по модулям компиляции: к ним можно получить доступ через Scala отражение api
ClassfileNnotations: они представляют аннотации, хранящиеся в виде аннотаций java. Если вы хотите получить к ним доступ через API Java Reflection, вы должны определить их на Java, хотя.
Вот пример:
@SerialVersionUID(1) class Blub
Теперь мы можем получить аннотацию следующим образом:
import scala.reflect.runtime.universe._
val a = typeOf[Blub].typeSymbol.annotations.head
То, что мы фактически получаем, не является экземпляром аннотации. Среда выполнения просто дает нам то, что написано в байтовом коде: код Scala, генерирующий аннотацию. Вы можете распечатать AST, который вы получаете:
showRaw(a.tree)
Теперь это уже довольно сложная структура, но мы можем разложить ее с помощью сопоставления с образцом:
val Apply(_, List(AssignOrNamedArg(_,Literal(Constant(value))))) = a.tree
val uid = value.asInstanceOf[Long]
Это нормально для очень простых аннотаций (но мы могли бы написать их в Java и полагаться на экземпляры JVM для нас). Что делать, если мы действительно хотим оценить этот код и создать экземпляр класса аннотаций? (Для @SerialVersionUID это не поможет нам, поскольку класс фактически не дает доступа к id...) Мы также можем это сделать:
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)
}
Обратите внимание, что это вызовет компилятор, который занимает немного времени, особенно если вы делаете это в первый раз. Сложное метапрограммирование должно выполняться во время компиляции в Scala. Многие вещи в Java выполняются только во время выполнения, потому что у вас есть только мета-программирование времени выполнения (ну, есть процессоры с аннотациями, но они более ограничены).