Scala 注释宏仅适用于预定义 类
Scala annotation macro only works with pre-defined classes
注意:下面有一个编辑!
注意:下面还有一个编辑!
我编写了一个 Scala 注释宏,它被传递给 class 并创建(或更确切地说填充)一个案例对象。案例对象的名称与传递的名称相同class。更重要的是,对于传递的class的每个字段,case对象中都会有一个同名的字段。然而,case 对象的字段都是 String
类型,它们的值是传递的 class 中相应字段类型的名称。示例:
// 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
然而,这似乎只适用于预定义的 class,例如 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
我做错了什么?我的宏代码可以在下面找到。此外,如果有人注意到 Scala 的不惯用语或一般的不良做法,我不会介意提示。非常感谢您!
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.")
}
}
}
编辑:正如 Eugene Burmako 所指出的,错误的发生是因为 class A
还没有被编译,所以它的 java.lang.Class
不存在。我现在已经开始悬赏 100 个 Whosebug 积分,奖励所有想知道如何让它发挥作用的人!
编辑 2:用例的一些背景:作为我学士论文的一部分,我正在研究 Scala DSL,用于表达对事件处理系统的查询。这些查询传统上表示为字符串,这会引发很多问题。典型的查询如下所示:"select A.id, B.timestamp from pattern[A -> B]"。含义:如果发生类型 A
的事件,然后发生类型 B
的事件,请给我 A
事件的 id
和 timestamp
B
事件。 A
和 B
类型通常是简单的 Java class 类型,我无法控制它们。 id
和 timestamp
是那些 class 的字段。我希望我的 DSL 查询看起来像这样:select (A.id, B.timestamp) { /* ... * / }
。这意味着对于表示事件类型的每个 class,例如 A
,我需要一个伴随对象——最好是同名的。此伴生对象应具有与相应 class 相同的字段,以便我可以将其字段传递给 select
函数,如下所示:select (A.id, B.timestamp) { /* ... * / }
。这样,如果我试图将 A.idd
传递给 select
函数,如果原始 class 中没有这样的字段,它将在编译时失败——因为那样就不会了成为伴随对象中的一个。
这不是您的宏问题的答案,但它可能是您的一般问题的解决方案。
如果您可以允许对 DSL 的语法进行微小的更改,则无需使用宏就可以实现这一点(取决于此问题中未提及的其他要求)。
scala> class Select[A,B]{
| def apply[R,S](fa: A => R, fb: B => S)(body: => Unit) = ???
| }
defined class Select
scala> def select[A,B] = new Select[A,B]
select: [A, B]=> Select[A,B]
scala> class MyA { def id = 42L }
defined class MyA
scala> class MyB { def timestamp = "foo" }
defined class MyB
scala> select[A,B](_.id, _.timestamp){ /* ... */ }
scala.NotImplementedError: an implementation is missing
我在这里使用 class Select
作为能够指定事件类型 class 的方法,同时让编译器推断函数的结果类型 fa
和 fb
。如果您不需要这些结果类型,您可以将其写为 def select[A,B](fa: A => Any, fb: B => Any)(body: => Unit) = ???
.
如有必要,您仍然可以将 select
或 apply
方法作为宏来实现。但是使用这种语法,你将不再需要生成带有宏注释的对象。
注意:下面有一个编辑! 注意:下面还有一个编辑!
我编写了一个 Scala 注释宏,它被传递给 class 并创建(或更确切地说填充)一个案例对象。案例对象的名称与传递的名称相同class。更重要的是,对于传递的class的每个字段,case对象中都会有一个同名的字段。然而,case 对象的字段都是 String
类型,它们的值是传递的 class 中相应字段类型的名称。示例:
// 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
然而,这似乎只适用于预定义的 class,例如 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
我做错了什么?我的宏代码可以在下面找到。此外,如果有人注意到 Scala 的不惯用语或一般的不良做法,我不会介意提示。非常感谢您!
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.")
}
}
}
编辑:正如 Eugene Burmako 所指出的,错误的发生是因为 class A
还没有被编译,所以它的 java.lang.Class
不存在。我现在已经开始悬赏 100 个 Whosebug 积分,奖励所有想知道如何让它发挥作用的人!
编辑 2:用例的一些背景:作为我学士论文的一部分,我正在研究 Scala DSL,用于表达对事件处理系统的查询。这些查询传统上表示为字符串,这会引发很多问题。典型的查询如下所示:"select A.id, B.timestamp from pattern[A -> B]"。含义:如果发生类型 A
的事件,然后发生类型 B
的事件,请给我 A
事件的 id
和 timestamp
B
事件。 A
和 B
类型通常是简单的 Java class 类型,我无法控制它们。 id
和 timestamp
是那些 class 的字段。我希望我的 DSL 查询看起来像这样:select (A.id, B.timestamp) { /* ... * / }
。这意味着对于表示事件类型的每个 class,例如 A
,我需要一个伴随对象——最好是同名的。此伴生对象应具有与相应 class 相同的字段,以便我可以将其字段传递给 select
函数,如下所示:select (A.id, B.timestamp) { /* ... * / }
。这样,如果我试图将 A.idd
传递给 select
函数,如果原始 class 中没有这样的字段,它将在编译时失败——因为那样就不会了成为伴随对象中的一个。
这不是您的宏问题的答案,但它可能是您的一般问题的解决方案。
如果您可以允许对 DSL 的语法进行微小的更改,则无需使用宏就可以实现这一点(取决于此问题中未提及的其他要求)。
scala> class Select[A,B]{
| def apply[R,S](fa: A => R, fb: B => S)(body: => Unit) = ???
| }
defined class Select
scala> def select[A,B] = new Select[A,B]
select: [A, B]=> Select[A,B]
scala> class MyA { def id = 42L }
defined class MyA
scala> class MyB { def timestamp = "foo" }
defined class MyB
scala> select[A,B](_.id, _.timestamp){ /* ... */ }
scala.NotImplementedError: an implementation is missing
我在这里使用 class Select
作为能够指定事件类型 class 的方法,同时让编译器推断函数的结果类型 fa
和 fb
。如果您不需要这些结果类型,您可以将其写为 def select[A,B](fa: A => Any, fb: B => Any)(body: => Unit) = ???
.
如有必要,您仍然可以将 select
或 apply
方法作为宏来实现。但是使用这种语法,你将不再需要生成带有宏注释的对象。