scala 2.10.2调用一个宏观方法'通用类型不起作用

时间:2013-12-24 15:48:22

标签: scala generics scala-macros

我定义了以下宏来将案例字段转换为地图

   import scala.language.experimental.macros
    import scala.reflect.macros.Context

    def asMap_impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = {
      import c.universe._

      val mapApply = Select(reify(Map).tree, newTermName("apply"))

      val pairs = weakTypeOf[T].declarations.collect {
        case m: MethodSymbol if m.isCaseAccessor =>
          val name = c.literal(m.name.decoded)
          val value = c.Expr(Select(t.tree, m.name))
          reify(name.splice -> value.splice).tree
      }

      c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))
    }

方法实现

def asMap[T](t: T) = macro asMap_impl[T]

然后我定义一个案例类来测试它

case class User(name : String)

它工作正常(使用scala repl):

 scala> asMap(User("foo")) res0:
 scala.collection.immutable.Map[String,String] = Map(name -> foo)

但是当我用另一种通用方法

包装此方法时
def printlnMap[T](t: T) = println(asMap(t))

此方法始终打印空地图:

scala> printlnMap(User("foo"))
Map()

类型信息似乎丢失了,如何让printlnMap打印所有字段?

1 个答案:

答案 0 :(得分:12)

这不起作用的原因是你的宏只会被调用一次 - 编译printlnMap函数时。这样它会将T视为抽象类型。每次调用printlnMap时都不会调用宏。

快速解决此问题的一种方法是将printlnMap也实现为宏。当然,这并不理想。所以,这是一个不同的方法 - 类型类实例的实现:

首先,定义一个类型类,它允许我们将case类实例转换为map:

trait CaseClassToMap[T] {
  def asMap(t: T): Map[String,Any]
}

然后,为某些案例类T实现一个宏实现此类型类的实例。您可以将其放入CaseClassToMap随播广告对象中,以便全局可见。

object CaseClassToMap {
  implicit def materializeCaseClassToMap[T]: CaseClassToMap[T] = macro impl[T]

  def impl[T: c.WeakTypeTag](c: Context): c.Expr[CaseClassToMap[T]] = {
    import c.universe._

    val mapApply = Select(reify(Map).tree, newTermName("apply"))

    val pairs = weakTypeOf[T].declarations.collect {
      case m: MethodSymbol if m.isCaseAccessor =>
        val name = c.literal(m.name.decoded)
        val value = c.Expr(Select(Ident(newTermName("t")), m.name))
        reify(name.splice -> value.splice).tree
      }

    val mapExpr = c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))

    reify {
      new CaseClassToMap[T] {
        def asMap(t: T) = mapExpr.splice
      }
    }
  }
}

现在,你可以这样做:

def asMap[T: CaseClassToMap](t: T) =
  implicitly[CaseClassToMap[T]].asMap(t)

def printlnMap[T: CaseClassToMap](t: T) =
  println(asMap(t))