Примечание: есть EDIT внизу! Примечание: есть еще один EDIT внизу!Макрос аннотации Scala работает только с заранее определенными классами
Я написал макрос Scala annotation, который передается классу и создает (или, скорее, заполняет) объект case. Имя объекта case совпадает с именем переданного класса. Что еще более важно, для каждого поля пройденного класса будет поле в объекте с тем же именем. Однако поля объекта case имеют тип String
, а их значение - имя типа соответствующего поля в переданном классе. Пример:
// Using the annotation macro to populate a case object called `String`
@RegisterClass(classOf[String]) case object String
// The class `String` defines a field called `value` of type `char[]`.
// The case object also has a field `value`, containing `"char[]"`.
println(String.value) // Prints `"char[]"` to the console
Это, однако, кажется, работает только с заранее определенными классами, такими как String
. Если я определяю case class A(...)
и попытаться сделать @RegisterClass(classOf[A]) case object A
, я получаю следующее сообщение об ошибке:
[info] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[info]
[info] not found: type A
Что я сделал не так? Код моего макроса можно найти ниже. Кроме того, если кто-то замечает не-идиоматическую Скалу или плохие практики вообще, я бы не прочь намек. Заранее большое спасибо!
class RegisterClass[T](clazz: Class[T]) extends StaticAnnotation {
def macroTransform(annottees: Any*) =
macro RegisterClass.expandImpl[T]
}
object RegisterClass {
def expandImpl[T](c: blackbox.Context)(annottees: c.Expr[Any]*) = {
import c.universe._
val clazz: Class[T] = c.prefix.tree match {
case q"new RegisterClass($clazz)" => c.eval[Class[T]](c.Expr(clazz))
case _ => c.abort(c.enclosingPosition, "RegisterClass: Annotation expects a Class[T] instance as argument.")
}
annottees.map(_.tree) match {
case List(q"case object $caseObjectName") =>
if (caseObjectName.toString != clazz.getSimpleName)
c.abort(c.enclosingPosition, "RegisterClass: Annotated case object and class T of passed Class[T] instance" +
"must have the same name.")
val clazzFields = clazz.getDeclaredFields.map(field => field.getName -> field.getType.getSimpleName).toList
val caseObjectFields = clazzFields.map(field => {
val fieldName: TermName = field._1
val fieldType: String = field._2
q"val $fieldName = $fieldType"
})
c.Expr[Any](q"case object $caseObjectName { ..$caseObjectFields }")
case _ => c.abort(c.enclosingPosition, "RegisterClass: Annotation must be applied to a case object definition.")
}
}
}
EDIT: Как Евгений Burmako отметил, ошибка происходит потому, что class A
не был составлен еще, так java.lang.Class
для него не существует. Теперь я начал зарабатывать 100 очков StackOverflow для всех, кто как идея, как можно заставить это работать!
EDIT 2: Некоторая предыстория в прецеденте: как часть моего бакалавриата, я работаю над DSL Scala для выражения запросов для систем обработки событий. Эти запросы традиционно выражаются как строки, что вызывает множество проблем. Типичный запрос будет выглядеть так: «выберите A.id, B.timestamp из шаблона [A -> B]». Значение: Если происходит событие типа A
, и после этого произойдет событие типа B
, дайте мне id
события A
и timestamp
события B
. Типы A
и B
обычно представляют собой простые классы Java, над которыми я не имею контроля. id
и timestamp
- поля этих классов. Я бы хотел, чтобы мои DSL выглядели так: select (A.id, B.timestamp) { /* ... */}
. Это означает, что для каждого класса, представляющего тип события, например, A
, мне нужен объект-компаньон - в идеале с тем же именем. Этот объект-компаньон должен иметь те же поля, что и соответствующий класс, поэтому я могу передать свои поля функции select
, например: select (A.id, B.timestamp) { /* ... */}
. Таким образом, если я попытался передать A.idd
функции select
, во время компиляции это не сработало бы, если бы не было такого поля в исходном классе, потому что тогда не было бы и одного в сопутствующем объекте.
Я не знаю, работает ли это на самом деле, но, возможно, это помогает: что, если вы добавите поле в объект case. I.e., '@RegisterClass case object String {type T = String}' или '@RegisterClass case object String {val t: String = _}'. Тогда, возможно, макрос аннотации может получить доступ к типу 'T' или типу' t' путем проверки аннотита? (Я не знаю, работает ли это, поэтому я не отправляю ответ, но, может быть, идея как-то помогает.) –
Пара идей: (1) Переписывание объектов в таком большом масштабе - это плохая идея, поскольку вы по существу скрытие/перезапись существующей функциональности. (2) Spark 2.0 делает что-то подобное (но не проверяет тип, что было бы неплохо) с [столбцами] (http://spark.apache.org/docs/latest/api/scala/index.html#org.apache .spark.sql.Column) внутри операций, таких как 'select',' join', 'groupby' (см. примеры [здесь] (http://spark.apache.org/docs/latest/api/scala/index.html # org.apache.spark.sql.Dataset)). (3) Почему бы не сделать для этого плагин компилятора? Затем вы можете «переписать» синтаксис Scala локально. – Alec