2016-08-22 6 views
0

注:下に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イベントのidBイベントのtimestampを与えてください。 ABのタイプは、通常、制御できない単純なJavaクラスです。 idおよびtimestampは、これらのクラスのフィールドです。私は自分のDSLの質問を次のようにしたい:select (A.id, B.timestamp) { /* ... */}。つまり、イベントタイプを表すすべてのクラス、たとえばAには、理想的には同じ名前の付随オブジェクトが必要です。このコンパニオンオブジェクトは、それぞれのクラスと同じフィールドを持つ必要があります。select関数にそのフィールドを渡すことができます。select (A.id, B.timestamp) { /* ... */}。このようにしてA.iddselect関数に渡そうとすると、元のクラスにそのようなフィールドがないとコンパイル時に失敗します。これは、コンパニオンオブジェクトにも1つも存在しないからです。

+0

これが実際に動作するかどうかわかりませんが、おそらく役立ちます。ケースオブジェクトにフィールドを追加するとどうなりますか?つまり、@RegisterClassのcaseオブジェクトの文字列{type T = String} 'または@RegisterClassのcaseオブジェクトの文字列{val t:String = _}'です。そして、おそらく注釈マクロは注釈を検査することによって型 'T'または型' t'にアクセスすることができますか? (私はこれが動作するかどうかわかりません、なぜ私は答えを投稿しませんが、おそらく考えは何とか役立ちます) –

+0

カップルの考え:(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

答えて

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 

は、私は、コンパイラが機能fafbの結果の型を推論させながら、あなたのイベントクラスの種類を指定できるようにする手段として、ここではクラスSelectを使用しています。これらの結果タイプが必要ない場合は、def select[A,B](fa: A => Any, fb: B => Any)(body: => Unit) = ???と書くことができます。

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

+0

このアプローチは素晴らしいです!私はちょうどもう一つ質問があります: 'select'関数のための任意の量のパラメータでこれを実現するScala風の方法は何でしょうか?だから、これを行うこともできます: 'select [A、B、C](_。foo、_.bar、_.baz)' – lambdarookie

+0

@scalarookieこれは実際に 'マクロ'を使うことができます。面白い。 –

関連する問題