Scala Language
マクロ
サーチ…
前書き
構文
- def x()=マクロx_impl // xはマクロで、x_implはコードを変換するために使われます
- def macroTransform(annottees:Any *):Any =マクロimpl //アノテーションでマクロを使用する
備考
マクロは、 scala.language.macros
をインポートscala.language.macros
か、コンパイラオプション-language:macros
使用して、有効にする必要がある言語機能です。マクロ定義だけがこれを必要とします。マクロを使用するコードでは必要ありません。
マクロ注釈
この単純なマクロ注釈は、注釈付きアイテムをそのまま出力します。
import scala.annotation.{compileTimeOnly, StaticAnnotation}
import scala.reflect.macros.whitebox.Context
@compileTimeOnly("enable macro paradise to expand macro annotations")
class noop extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro linkMacro.impl
}
object linkMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
c.Expr[Any](q"{..$annottees}")
}
}
@compileTimeOnly
アノテーションは、このマクロを使用するためにparadise
コンパイラプラグインをインクルードする必要があることを示すメッセージでエラーを生成します。 SBTを介してこれを含める手順はここにあります 。
上記のマクロは、次のように使用できます。
@noop
case class Foo(a: String, b: Int)
@noop
object Bar {
def f(): String = "hello"
}
@noop
def g(): Int = 10
メソッドマクロ
メソッドがマクロであると定義されている場合、コンパイラは引数として渡されたコードを受け取り、それをASTに変換します。次に、そのASTを使用してマクロ実装を呼び出し、新しいASTを返します。新しいASTがコールサイトにスプライスされます。
import reflect.macros.blackbox.Context
object Macros {
// This macro simply sees if the argument is the result of an addition expression.
// E.g. isAddition(1+1) and isAddition("a"+1).
// but !isAddition(1+1-1), as the addition is underneath a subtraction, and also
// !isAddition(x.+), and !isAddition(x.+(a,b)) as there must be exactly one argument.
def isAddition(x: Any): Boolean = macro isAddition_impl
// The signature of the macro implementation is the same as the macro definition,
// but with a new Context parameter, and everything else is wrapped in an Expr.
def isAddition_impl(c: Context)(expr: c.Expr[Any]): c.Expr[Boolean] = {
import c.universe._ // The universe contains all the useful methods and types
val plusName = TermName("+").encodedName // Take the name + and encode it as $plus
expr.tree match { // Turn expr into an AST representing the code in isAddition(...)
case Apply(Select(_, `plusName`), List(_)) => reify(true)
// Pattern match the AST to see whether we have an addition
// Above we match this AST
// Apply (function application)
// / \
// Select List(_) (exactly one argument)
// (selection ^ of entity, basically the . in x.y)
// / \
// _ \
// `plusName` (method named +)
case _ => reify(false)
// reify is a macro you use when writing macros
// It takes the code given as its argument and creates an Expr out of it
}
}
}
Tree
を引数として取るマクロを持つことも可能です。 Expr
を作成するためにreify
がどのように使用されるかのように、 q
(quasiquote)文字列補間器はTree
作成して分解することができます。上記のq
を使用することもできたことに注意してください( expr.tree
は驚くexpr.tree
ですが、 Tree
そのものです)が、実証目的ではありませんでした。
// No Exprs, just Trees
def isAddition_impl(c: Context)(tree: c.Tree): c.Tree = {
import c.universe._
tree match {
// q is a macro too, so it must be used with string literals.
// It can destructure and create Trees.
// Note how there was no need to encode + this time, as q is smart enough to do it itself.
case q"${_} + ${_}" => q"true"
case _ => q"false"
}
}
マクロ内のエラー
マクロは、 Context
使用してコンパイラの警告とエラーを引き起こす可能性があります。
私たちは、悪いコードについては特に大変だとし、コンパイラの情報メッセージで技術的な負債のすべてのインスタンスをマークしたいとします(このアイデアがどれほど悪いかについては考えません)。このようなメッセージを出す以外は何もしないマクロを使うことができます。
import reflect.macros.blackbox.Context
def debtMark(message: String): Unit = macro debtMark_impl
def debtMarkImpl(c: Context)(message: c.Tree): c.Tree = {
message match {
case Literal(Constant(msg: String)) => c.info(c.enclosingPosition, msg, false)
// false above means "do not force this message to be shown unless -verbose"
case _ => c.abort(c.enclosingPosition, "Message must be a string literal.")
// Abort causes the compilation to completely fail. It's not even a compile error, where
// multiple can stack up; this just kills everything.
}
q"()" // At runtime this method does nothing, so we return ()
}
さらに、 ???
を使用する代わりに実装されていないコードをマークするために、我々は、二つのマクロを作成することができます!!!
と?!?
同じ目的を果たしますが、コンパイラの警告を出します。 ?!?
警告が出され、 !!!
完全なエラーが発生します。
import reflect.macros.blackbox.Context
def ?!? : Nothing = macro impl_?!?
def !!! : Nothing = macro impl_!!!
def impl_?!?(c: Context): c.Tree = {
import c.universe._
c.warning(c.enclosingPosition, "Unimplemented!")
q"${termNames.ROOTPKG}.scala.Predef.???"
// If someone were to shadow the scala package, scala.Predef.??? would not work, as it
// would end up referring to the scala that shadows and not the actual scala.
// ROOTPKG is the very root of the tree, and acts like it is imported anew in every
// expression. It is actually named _root_, but if someone were to shadow it, every
// reference to it would be an error. It allows us to safely access ??? and know that
// it is the one we want.
}
def impl_!!!(c: Context): c.Tree = {
import c.universe._
c.error(c.enclosingPosition, "Unimplemented!")
q"${termNames.ROOTPKG}.scala.Predef.???"
}