【问题标题】:Scala annotation macro only works with pre-defined classesScala 注释宏仅适用于预定义的类
【发布时间】:2016-08-22 19:48:47
【问题描述】:

注意:下面有一个编辑! 注意:下面还有另一个 EDIT!

我编写了一个 Scala 注释宏,它被传递给一个类并创建(或更确切地说是填充)一个案例对象。案例对象的名称与传递的类的名称相同。更重要的是,对于传递的类的每一个字段,都会有一个同名的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

我做错了什么?我的宏的代码可以在下面找到。此外,如果有人注意到不惯用的 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 个 StackOverflow 积分!

编辑 2:用例的一些背景知识:作为我的学士论文的一部分,我正在研究 Scala DSL,用于表达事件处理系统的查询。这些查询传统上表示为字符串,这会引发很多问题。典型的查询如下所示:“从模式 [A -> B] 中选择 A.id, B.timestamp”。含义:如果发生A 类型的事件,然后也发生B 类型的事件,请给我A 事件的idB 事件的timestampAB 类型通常是我无法控制的简单 Java 类。 idtimestamp 是这些类的字段。我希望我的 DSL 查询看起来像这样:select (A.id, B.timestamp) { /* ... * / }。这意味着对于每个代表事件类型的类,例如A,我需要一个伴随对象——最好是同名的。这个伴生对象应该具有与相应类相同的字段,以便我可以将其字段传递给select 函数,如下所示:select (A.id, B.timestamp) { /* ... * / }。这样,如果我试图将A.idd 传递给select 函数,如果原始类中没有这样的字段,它将在编译时失败——因为那样的话伴生对象中也不会有这样的字段。

【问题讨论】:

  • 我不知道这是否真的有效,但也许它会有所帮助:如果您向案例对象添加一个字段会怎样。即@RegisterClass case object String { type T = String }@RegisterClass case object String { val t : String = _ }。那么注释宏是否可以通过检查被注释者来访问T 类型或t 类型? (我不知道这是否有效,这就是我不发布答案的原因,但也许这个想法会有所帮助。)
  • 几个想法:(1)如此大规模地重写对象是一个坏主意,因为您实际上是在隐藏/覆盖现有功能。 (2) Spark 2.0 使用columns 内部操作,如selectjoingroupby(参见示例here)做了类似的事情(但没有进行类型检查,这会很好)。 (3) 为什么不为此制作一个编译器插件?然后你可以在本地“重写”Scala 语法。

标签: scala macros scala-macros scala-macro-paradise


【解决方案1】:

这不能解决您的宏观问题,但可以解决您的一般问题。
如果您可以允许对 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

我在这里使用类Select 作为一种方法,以便能够指定事件类的类型,同时让编译器推断函数fafb 的结果类型。如果您不需要这些结果类型,您可以将其写为def select[A,B](fa: A => Any, fb: B => Any)(body: => Unit) = ???

如有必要,您仍然可以将selectapply 方法作为宏实现。但是使用这种语法,您将不再需要生成带有宏注释的对象。

【讨论】:

  • 这种方法太棒了!我还有一个问题:使用select 函数的任意数量的参数来实现这一点的Scala 式方法是什么?所以也可以这样做:select[A, B, C](_.foo, _.bar, _.baz)
  • @scalarookie 为此,您实际上可以使用macro。好笑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多