Scala宏观举的标志或类型

时间:2015-09-17 23:07:40

标签: scala scala-macros

我试图编写一个宏,它可以使用有关类字段的信息来生成模式。例如,假设我有一个名为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得到了一个很棒的答案,但我的实现不能仅限于传递字符串,因为我需要更多类型信息来生成模式的详细信息,这是我认为我的困惑在于。

1 个答案:

答案 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()))))