传递给Scala宏的内省参数
我想编写一个Scala宏,它以case类的一个实例作为参数。所有可以传递给宏的对象都必须实现特定的标记特征 以下代码段显示了marker trait和实现它的两个示例案例类:传递给Scala宏的内省参数,scala,macros,scala-macros,Scala,Macros,Scala Macros,我想编写一个Scala宏,它以case类的一个实例作为参数。所有可以传递给宏的对象都必须实现特定的标记特征 以下代码段显示了marker trait和实现它的两个示例案例类: trait Domain case class Country( id: String, name: String ) extends Domain case class Town( id: String, longitude: Double, latitude: Double ) extends Domain 现在,我
trait Domain
case class Country( id: String, name: String ) extends Domain
case class Town( id: String, longitude: Double, latitude: Double ) extends Domain
现在,我想使用宏编写以下代码,以避免运行时反射的严重性及其线程的不安全性:
object Test extends App {
// instantiate example domain object
val myCountry = Country( "CH", "Switzerland" )
// this is a macro call
logDomain( myCountry )
}
宏logDomain
在不同的项目中实现,看起来类似于:
object Macros {
def logDomain( domain: Domain ): Unit = macro logDomainMacroImpl
def logDomainMacroImpl( c: Context )( domain: c.Expr[Domain] ): c.Expr[Unit] = {
// Here I would like to introspect the argument object but do not know how?
// I would like to generate code that prints out all val's with their values
}
}
宏的目的应该是生成代码,该代码在运行时输出给定对象的所有值(id
和name
),并打印它们,如下所示:
id (String) : CH
name (String) : Switzerland
要实现这一点,我必须动态检查传递的类型参数并确定其成员(VAL)。然后我必须生成一个表示创建日志输出的代码的AST。无论将实现标记特征“域”的特定对象传递给宏,宏都应该工作
在这一点上我迷路了。如果有人能给我一个起点或给我一些文档,我将不胜感激?我对Scala比较陌生,在Scala API文档或宏指南中还没有找到解决方案。列出case类的访问器是使用宏时的常见操作,因此我倾向于保留如下方法:
def accessors[A: u.WeakTypeTag](u: scala.reflect.api.Universe) = {
import u._
u.weakTypeOf[A].declarations.collect {
case acc: MethodSymbol if acc.isCaseAccessor => acc
}.toList
}
这将为我们提供A
的所有case类访问器方法符号(如果有)。请注意,我在这里使用的是通用反射API,现在还没有必要使这个宏特定
我们可以用其他一些方便的东西来概括这个方法:
trait ReflectionUtils {
import scala.reflect.api.Universe
def accessors[A: u.WeakTypeTag](u: Universe) = {
import u._
u.weakTypeOf[A].declarations.collect {
case acc: MethodSymbol if acc.isCaseAccessor => acc
}.toList
}
def printfTree(u: Universe)(format: String, trees: u.Tree*) = {
import u._
Apply(
Select(reify(Predef).tree, "printf"),
Literal(Constant(format)) :: trees.toList
)
}
}
现在我们可以非常简洁地编写实际的宏代码:
trait Domain
object Macros extends ReflectionUtils {
import scala.language.experimental.macros
import scala.reflect.macros.Context
def log[D <: Domain](domain: D): Unit = macro log_impl[D]
def log_impl[D <: Domain: c.WeakTypeTag](c: Context)(domain: c.Expr[D]) = {
import c.universe._
if (!weakTypeOf[D].typeSymbol.asClass.isCaseClass) c.abort(
c.enclosingPosition,
"Need something typed as a case class!"
) else c.Expr(
Block(
accessors[D](c.universe).map(acc =>
printfTree(c.universe)(
"%s (%s) : %%s\n".format(
acc.name.decoded,
acc.typeSignature.typeSymbol.name.decoded
),
Select(domain.tree.duplicate, acc.name)
)
),
c.literalUnit.tree
)
)
}
}
或:
根据需要。在我看来,您需要解决两个问题:1)从宏参数获取必要的信息,2)生成表示所需代码的树 在Scala2.10中,这些工作是通过反射API完成的。请按照下面的说明查看有哪些文档可供使用
import scala.reflect.macros.Context
import language.experimental.macros
trait Domain
case class Country(id: String, name: String) extends Domain
case class Town(id: String, longitude: Double, latitude: Double) extends Domain
object Macros {
def logDomain(domain: Domain): Unit = macro logDomainMacroImpl
def logDomainMacroImpl(c: Context)(domain: c.Expr[Domain]): c.Expr[Unit] = {
import c.universe._
// problem 1: getting the list of all declared vals and their types
// * declarations return declared, but not inherited members
// * collect filters out non-methods
// * isCaseAccessor only leaves accessors of case class vals
// * typeSignature is how you get types of members
// (for generic members you might need to use typeSignatureIn)
val vals = typeOf[Country].declarations.toList.collect{ case sym if sym.isMethod => sym.asMethod }.filter(_.isCaseAccessor)
val types = vals map (_.typeSignature)
// problem 2: generating the code which would print:
// id (String) : CH
// name (String) : Switzerland
//
// usually reify is of limited usefulness
// (see https://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause)
// but here it's perfectly suitable
// a subtle detail: `domain` will be possibly used multiple times
// therefore we need to duplicate it
val stmts = vals.map(v => c.universe.reify(println(
c.literal(v.name.toString).splice +
"(" + c.literal(v.returnType.toString).splice + ")" +
" : " + c.Expr[Any](Select(domain.tree.duplicate, v)).splice)).tree)
c.Expr[Unit](Block(stmts, Literal(Constant(()))))
}
}
你赢了我5分钟!:)非常感谢您的详细回答!你的例子正是我想要的。今晚我要试试。解决方案的优点在于使用了type参数和WeakTypeTag,这使代码完全通用。它应该适用于任何实现“域”的case类。非常感谢您的详细回答。我越来越喜欢Scala,新的反射和宏支持非常好。我将更多地使用它,并尝试生成一些代码,使用case-vals的提取值实例化不同的对象。
scala> Macros.log(Country("CH", "Switzerland"))
id (String) : CH
name (String) : Switzerland
import scala.reflect.macros.Context
import language.experimental.macros
trait Domain
case class Country(id: String, name: String) extends Domain
case class Town(id: String, longitude: Double, latitude: Double) extends Domain
object Macros {
def logDomain(domain: Domain): Unit = macro logDomainMacroImpl
def logDomainMacroImpl(c: Context)(domain: c.Expr[Domain]): c.Expr[Unit] = {
import c.universe._
// problem 1: getting the list of all declared vals and their types
// * declarations return declared, but not inherited members
// * collect filters out non-methods
// * isCaseAccessor only leaves accessors of case class vals
// * typeSignature is how you get types of members
// (for generic members you might need to use typeSignatureIn)
val vals = typeOf[Country].declarations.toList.collect{ case sym if sym.isMethod => sym.asMethod }.filter(_.isCaseAccessor)
val types = vals map (_.typeSignature)
// problem 2: generating the code which would print:
// id (String) : CH
// name (String) : Switzerland
//
// usually reify is of limited usefulness
// (see https://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause)
// but here it's perfectly suitable
// a subtle detail: `domain` will be possibly used multiple times
// therefore we need to duplicate it
val stmts = vals.map(v => c.universe.reify(println(
c.literal(v.name.toString).splice +
"(" + c.literal(v.returnType.toString).splice + ")" +
" : " + c.Expr[Any](Select(domain.tree.duplicate, v)).splice)).tree)
c.Expr[Unit](Block(stmts, Literal(Constant(()))))
}
}