注:下にEDITがあります。 注:別のEDITがあります。スカラアノテーションマクロは、あらかじめ定義されたクラスでしか動作しません。
私はスカラアノテーションマクロを作成しました。このマクロはクラスを渡されており、ケースオブジェクトを作成します。 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.")
}
}
}
EDIT:ユージンBurmakoが指摘したように、class A
がまだコンパイルされていないため、エラーが発生したので、それについてjava.lang.Class
が存在しません。私は今、どのようにこれを動作させることができるかというアイディアとして、100 StackOverflowポイントの賞金を始めました!
EDIT 2:ユースケースの背景:私の学士論文の一部として、イベント処理システムのクエリを表現するためのScala DSLに取り組んでいます。これらのクエリは伝統的に文字列として表現され、多くの問題を引き起こします。典型的なクエリは、 "Aid、B.timestamp from pattern [A - > B]"を選択します。意味:タイプがA
のイベントが発生した後、タイプがのイベントが発生した場合は、A
イベントのid
とB
イベントのtimestamp
を与えてください。 A
とB
のタイプは、通常、制御できない単純なJavaクラスです。 id
およびtimestamp
は、これらのクラスのフィールドです。私は自分のDSLの質問を次のようにしたい:select (A.id, B.timestamp) { /* ... */}
。つまり、イベントタイプを表すすべてのクラス、たとえばA
には、理想的には同じ名前の付随オブジェクトが必要です。このコンパニオンオブジェクトは、それぞれのクラスと同じフィールドを持つ必要があります。select
関数にそのフィールドを渡すことができます。select (A.id, B.timestamp) { /* ... */}
。このようにしてA.idd
をselect
関数に渡そうとすると、元のクラスにそのようなフィールドがないとコンパイル時に失敗します。これは、コンパニオンオブジェクトにも1つも存在しないからです。
これが実際に動作するかどうかわかりませんが、おそらく役立ちます。ケースオブジェクトにフィールドを追加するとどうなりますか?つまり、@RegisterClassのcaseオブジェクトの文字列{type T = String} 'または@RegisterClassのcaseオブジェクトの文字列{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'のような操作の中で実行します(例[here](http://spark.apache.org/docs/latest/api/scala/index.html参照)。 #org.apache.spark.sql.Dataset))。 (3)なぜかコンパイラのプラグインを作ってみませんか?次に、Scala構文をローカルに「書き直す」ことができます。 – Alec