MACRO PARADISE
Eugene Burmako 著
Eugene Yokota 訳
マクロアノテーションは現在マクロパラダイスプラグインを使った Scala 2.10 からのみ利用可能だ。 Scala 2.11 にこの機能が含まれる可能性はあるが、この件に関してのロードマップははっきりしてない。 マクロパラダイスページの説明にしたがってコンパイラプラグインをダウンロードしてほしい。
マクロアノテーションの現行の実装は実験的なものであり (それ故、現行のマクロパラダイスのバージョンも 2.0.0-SNAPSHOT
というスナップショット扱いされている)、今後の Scala 2.11 もしくは 2.12 への Scala improvement proposal を提出する上でのプレビューそして「たたき台」としての役割を果たすことを目的としている。
コード生成のニーズを満たすことができるか是非試してみて、何かうまくいかない点があれば Twitter で著者に報告してほしい。
マクロアノテーションは定義レベルでテキスト抽象化を実現する。Scala がマクロだと認識可能な定義であればトップレベルでも入れ子の定義でも、このアノテーションを付けることで (1つまたは複数の) メンバに展開させることができる。マクロパラダイスの以前のバージョンと比較して、2.0 のマクロパラダイスは以下の点を改善した:
これでコード生成に関して様々な可能性が広がったと言える。
この項では、役に立たないけども例としては便利な、注釈対象をログに書き込むこと以外は何もしないマクロを書いてみよう。
最初のステップは、StaticAnnotation
を継承して、macroTransform
マクロを定義する。
(この本文の ???
は 2.10.2 以降から使えるものだ。)
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
class identity extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro ???
}
macroTransform
マクロは型指定の無い (untyped) 注釈対象を受け取り (Scala には他に記法が無いためこのシグネチャの型は Any
となる)、単数もしくは複数の結果を生成する (単数の結果はそのまま返せるが、複数の結果の場合はリフレクション API に他に良い記法が無いため Block
にラッピングして返す)。
この時点で、一つの注釈対象に対して単一の結果は分かるが、複数対複数のマッピングがどのようになるのか疑問に思っている方もいるだろう。この過程はルールによって決定される:
以下に、identity
アノテーションマクロの実装例を示す。
@identity
が値か型パラメータに適用された場合のことも考慮に入れる必要があるため、ロジックは少し複雑になっている。コンパイラプラグイン側からは容易に標準ライブラリを変更できないため、このボイラープレートをヘルパー内でカプセル化できなかったため、解法がローテクになっていることは許してほしい。
(ちなみに、このボイラープレートそのものも適切なアノテーションマクロによって抽象化できるはずなので、将来的にはそのようなマクロが提供できるかもしれない。)
object identityMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
val (annottee, expandees) = inputs match {
case (param: ValDef) :: (rest @ (_ :: _)) => (param, rest)
case (param: TypeDef) :: (rest @ (_ :: _)) => (param, rest)
case _ => (EmptyTree, inputs)
}
println((annottee, expandees))
val outputs = expandees
c.Expr[Any](Block(outputs, Literal(Constant(()))))
}
}
コード例 | 表示 |
---|---|
@identity class C |
(<empty>, List(class C)) |
@identity class D; object D |
(<empty>, List(class D, object D)) |
class E; @identity object E |
(<empty>, List(object E)) |
def twice[@identity T] |
(type T, List(def twice)) |
Scala マクロの精神に則り、マクロアノテーションは柔軟性のために可能な限り型指定を無くし (untyped; マクロ展開前に型検査を必須としないこと)、利便性のために可能な限り型付けた (typed; マクロ展開前に利用可能な型情報を取得すること)。注釈対象は型指定が無いため、後付けでシグネチャ (例えばクラスメンバのリストなど) を変更できる。しかし、Scala マクロを書くということはタイプチェッカと統合するということであり、マクロアノテーションもそれは同じだ。そのため、マクロ展開時には全ての型情報を得ることができる (例えば、包囲するプログラムに対してリフレクションを使ったり、現行スコープ内から型検査を行ったり、implicit の検索を行うことができる)。
blog comments powered by DisqusContents