我想编写一个将案例类的实例作为参数的 Scala 宏。所有可以传递给宏的对象都必须实现特定的标记特征。
以下片段显示了标记特征和实现它的两个示例案例类:
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
为此,我必须动态检查传递的类型参数并确定其成员(vals)。然后我必须生成一个代表创建日志输出的代码的 AST。无论将实现标记特征“域”的特定对象传递给宏,宏都应该工作。
在这一点上,我迷路了。如果有人能给我一个起点或指向我一些文档,我将不胜感激?我对 Scala 比较陌生,在 Scala API 文档或宏指南中没有找到解决方案。
最佳答案
当您使用宏时,列出案例类的访问器是一种常见的操作,我倾向于保留这样的方法:
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
的所有案例类访问器方法符号。 ,如果有的话。请注意,我在这里使用的是通用反射 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
)
)
}
}
请注意,我们仍然需要跟踪我们正在处理的特定案例类类型,但类型推断将在调用站点处理这一点——我们不需要显式指定类型参数。
现在我们可以打开一个 REPL,粘贴您的案例类定义,然后编写以下内容:
scala> Macros.log(Town("Washington, D.C.", 38.89, 77.03))
id (String) : Washington, D.C.
longitude (Double) : 38.89
latitude (Double) : 77.03
或者:
scala> Macros.log(Country("CH", "Switzerland"))
id (String) : CH
name (String) : Switzerland
如预期的。
关于scala - 传递给 Scala 宏的自省(introspection)参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14325282/