我试图编写一个宏,该宏可以使用有关类的字段的信息来生成模式。例如,假设我有一个名为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)
}
}
""")
}
}但是,为字段创建qqs会导致一个可提升的错误或一个引用错误。昨天我问了一个类似的问题,Scala macros Type or Symbol lifted得到了一个很棒的答案,但是我的实现不能局限于传递String,因为我需要更多的类型信息来生成模式的细节,这就是我认为我的困惑所在。
发布于 2015-09-25 12:49:27
我认为您主要的困惑是,您似乎并不完全了解宏是如何处理的。这完全是编译时间。当宏运行时,它可以访问有关已编译程序的信息,包括Symbol。然后宏生成一些代码,这些代码将成为程序的一部分,但是生成的代码本身不能对Symbol或宏可以访问的任何其他代码进行更多的访问。
因此,在您的示例中,MsonType.apply将没有任何用处,因为输入是一个仅在宏中可用的Symbol (在编译时),而输出是运行时需要的MsonSchema。有意义的是将返回类型从MsonType更改为c.Expr[MsonType] (或简单地更改为c.universe.Tree),或者换句话说,MsonType.apply现在将获取一个符号并返回表示实例的树(而不是返回MsonType实例),然后宏可以调用它,并将其包含在返回给编译器的最终树中(编译器随后将其包含在调用站点的程序中)。在这种情况下,我认为最好完全删除MsonType.apply,并在Symbol宏中实现从Symbol到c.universe.Tree的转换。
这种转换实际上非常容易。构建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()))))https://stackoverflow.com/questions/32641337
复制相似问题