我定义了以下宏来将case字段转换为map
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打印所有字段?
这不起作用的原因是你的宏只会被调用一次 - 编译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))