0

注:以下に編集があります! 注:下に別の編集があります!

私は、クラスに渡されてケース オブジェクトを作成する (またはむしろ入力する) Scala 注釈マクロを作成しました。ケース オブジェクトの名前は、渡されたクラスの名前と同じです。さらに重要なことは、渡されたクラスのすべてのフィールドに対して、同じ名前のケース オブジェクトにフィールドが存在することです。ただし、ケース オブジェクトのフィールドはすべて typeStringであり、それらの値は、渡されたクラスのそれぞれのフィールドの型の名前です。例:

// 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です。a を定義して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.Classfor it が存在しません。私は今、これを機能させる方法を考えているすべての人のために、100 StackOverflow ポイントの報奨金を開始しました!

編集 2: ユースケースの背景: 学士論文の一部として、イベント処理システムのクエリを表現するための Scala DSL に取り組んでいます。これらのクエリは伝統的に文字列として表現されているため、多くの問題が発生します。典型的なクエリは次のようになります: 「select A.id, B.timestamp from pattern[A -> B]」。意味: のイベントがA発生し、その後Bも同様のイベントが発生した場合はid、そのイベントの とそのイベントAのを教えてください。型と通常は、私が制御できない単純な Java クラスです。およびそれらのクラスのフィールドです。DSL のクエリを次のようにしたいと思います。これは、イベント タイプを表すすべてのクラスについて、たとえば、timestampBABidtimestampselect (A.id, B.timestamp) { /* ... * / }A、理想的には同じ名前のコンパニオン オブジェクトが必要です。selectこのコンパニオン オブジェクトは、次のようにフィールドを関数に渡すことができるように、それぞれのクラスと同じフィールドを持つ必要がありますselect (A.id, B.timestamp) { /* ... * / }A.iddこのように、関数に渡そうとするselectと、元のクラスにそのようなフィールドがないとコンパイル時に失敗します。これは、コンパニオン オブジェクトにもフィールドがないためです。

4

1 に答える 1

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、イベント クラスの型を指定できるようにすると同時に、コンパイラに関数faとの結果の型を推測させますfb。これらの結果タイプが必要ない場合は、次のように記述できますdef select[A,B](fa: A => Any, fb: B => Any)(body: => Unit) = ???

必要に応じて、selectまたはapplyメソッドをマクロとして実装することもできます。ただし、この構文を使用すると、マクロ注釈を使用してオブジェクトを生成する必要がなくなります。

于 2016-08-29T11:18:07.650 に答える