问题描述
我定义以下宏以将案例字段转换为映射
I define following macro to transform case fields to 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]
然后我定义一个案例类进行测试
Then I define a case class to test it
case class User(name : String)
工作正常(与scala repl一起使用):
It works fine(with scala repl):
scala> asMap(User("foo")) res0:
scala.collection.immutable.Map[String,String] = Map(name -> foo)
但是当我将此方法与其他通用方法包装在一起
def printlnMap[T](t: T) = println(asMap(t))
此方法始终打印空白地图:
This method always print empty map:
scala> printlnMap(User("foo"))
Map()
类型信息似乎丢失了,如何获取printlnMap来打印所有字段?
The type information seems lost, how to get the printlnMap to print all fields ?
推荐答案
之所以不起作用,是因为在编译printlnMap
函数时,您的宏只会被调用一次.这样,它将把T
视为抽象类型.每次调用printlnMap
时都不会调用该宏.
The reason why this doesn't work is that your macro will be called only once - when compiling printlnMap
function. This way it will see T
as an abstract type. The macro will not be called on each invocation of printlnMap
.
一种快速解决此问题的方法是将printlnMap
也实现为宏.当然,这不是理想的.因此,这是另一种方法-类型类实例的实现:
One way to quickly fix this is to implement printlnMap
also as a macro. Of course, this is not ideal. So, here's a different approach - materialization of typeclass instances:
首先,定义一个类型类,使我们可以将案例类实例转换为地图:
First, define a typeclass that will allow us to convert case class instances to maps:
trait CaseClassToMap[T] {
def asMap(t: T): Map[String,Any]
}
然后,实现一个宏,该宏将为某些案例类T
实现该类型类的实例.您可以将其放入CaseClassToMap
随播对象中,以便全局可见.
Then, implement a macro that will materialize an instance of this type class for some case class T
. You can put it into CaseClassToMap
companion object so that it is visible globally.
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
}
}
}
}
现在,您可以执行以下操作:
Now, you can do this:
def asMap[T: CaseClassToMap](t: T) =
implicitly[CaseClassToMap[T]].asMap(t)
def printlnMap[T: CaseClassToMap](t: T) =
println(asMap(t))
这篇关于Scala 2.10.2调用具有通用类型的“宏方法"不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!