我试图编写一个宏,它可以使用有关类字段的信息来生成模式。例如,假设我有一个名为SchemaWriter[T]
的类型类,我希望使用宏来生成实现。
trait SchemaWriter[T] {
def schema: org.bibble.MsonSchema
}
case class MsonSchema(fields: Seq[MsonType])
case class MsonType(name: String, `type`: Class[_]) // might want other stuff here too, derived from a symbol or type-signature
object MsonType {
def apply(symbol:Symbol): MsonType = ...
}
我的想法是我的宏会吐出类似于:
的代码class FooSchemaWriter extends SchemaWriter[Foo] {
def schema : org.bibble.MsonSchema= {
val fields = for (symbol <- fields of class) yield {
MsonType(symbol)
}
org.bibble.MsonSchema(fields)
}
}
我可以实现一个宏,例如:
object Macros {
def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = {
import c.universe._
val T = weakTypeOf[T]
val fields = T.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head
val fieldTrees: [Tree] = fields.map { f =>
q"""org.bibble.MsonType(${f})"""
}
c.Expr[SchemaWriter[T]]( q"""
new SchemaWriter[$T] {
def schema = {
val fields = Seq(..$fieldTrees)
org.bibble.MsonSchema(fields)
}
}
""")
}
}
但是为字段创建qq会导致生命周期错误或非引号错误。昨天我问了一个类似的问题Scala macros Type or Symbol lifted得到了一个很棒的答案,但我的实现不能仅限于传递字符串,因为我需要更多类型信息来生成模式的详细信息,这是我认为我的困惑在于。
答案 0 :(得分:1)
我认为您的主要困惑在于您似乎并未完全了解宏的处理方式。
这完全是编译时间。当您的宏运行时,它可以访问有关已编译程序的信息,包括Symbol
s。
宏然后会生成一些代码,这些代码将成为程序的一部分,但是生成的代码本身并没有更多的访问权限
Symbol
或宏可以访问的任何其他内容。
因此,您的示例中的MsonType.apply
将没有任何用处,因为输入是Symbol
,仅在宏中可用(在编译时),
并且输出是MsonSchema
,您在运行时需要它。有意义的是将返回类型从MsonType
更改为c.Expr[MsonType]
(或简称为c.universe.Tree
),
换句话说,MsonType.apply
现在会采用符号并返回表示MsonType
实例的树
(而不是返回MsonType
实例),然后您的宏可以调用它并将其包含在返回给编译器的最终树中
(然后编译器将在您的程序中包含在调用站点)。
在这种特殊情况下,我认为最好只删除MsonType.apply
,然后从Symbol
实施转换
c.universe.Tree
宏中的writerImpl
。
这种转换实际上非常简单。构建MsonType
所需的只是字段名称和Class
实例,所以你去了:
q"""org.bibble.MsonType(${f.name.decoded}, classOf[${f.typeSignature}])"""
对于foo
类型的字段Bar
,这将生成一个表示以下表达式的树:
org.bibble.MsonType("foo", classOf[Bar])
就是这样,我们很高兴。
您将在下面找到完整的实施方案。请注意,我已经冒昧地改变了模式类型的定义,这种方式对我来说更具逻辑性和通用性。 特别是,每个字段现在都有相关的模式(鉴于您之前的问题,这显然是您首先想要的)。
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.language.experimental.macros
import scala.reflect.macros._
case class MsonSchema(`type`: Class[_], fields: Seq[MsonField])
case class MsonField(name: String, schema: MsonSchema)
trait SchemaWriter[T] {
def schema: MsonSchema
}
object SchemaWriter {
def apply[T:SchemaWriter]: SchemaWriter[T] = implicitly
implicit def defaultWriter[T] = macro Macros.writerImpl[T]
}
object Macros {
def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = {
import c.universe._
c.Expr[SchemaWriter[T]](generateSchemaWriterTree(c)(weakTypeOf[T]))
}
private def generateSchemaWriterTree(c: Context)(T: c.universe.Type): c.universe.Tree = {
import c.universe._
val fields = T.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head
val MsonFieldSym = typeOf[MsonField].typeSymbol
val SchemaWriterSym = typeOf[SchemaWriter[_]].typeSymbol
val fieldTrees: Seq[Tree] = fields.map { f =>
q"""new $MsonFieldSym(
${f.name.decoded},
_root_.scala.Predef.implicitly[$SchemaWriterSym[${f.typeSignature}]].schema
)"""
}
c.resetLocalAttrs(q"""
new $SchemaWriterSym[$T] {
val schema = MsonSchema(classOf[$T], Seq(..$fieldTrees))
}
""")
}
}
// Exiting paste mode, now interpreting.
warning: there were 7 deprecation warnings; re-run with -deprecation for details
warning: there was one feature warning; re-run with -feature for details
import scala.language.experimental.macros
import scala.reflect.macros._
defined class MsonSchema
defined class MsonField
defined trait SchemaWriter
defined object SchemaWriter
defined object Macros
scala> case class Foo(ab: String, cd: Int)
defined class Foo
scala> SchemaWriter[Foo].schema
res0: MsonSchema = MsonSchema(class Foo,List(MsonField(ab,MsonSchema(class java.lang.String,List())), MsonField(cd,MsonSchema(int,List()))))