scala 2.10.2 calling a 'macro method' with generic type not work

こ雲淡風輕ζ 提交于 2019-12-04 11:18:30

问题


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))
    }

And the method implementation

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

Then I define a case class to test it

case class User(name : String)

It works fine(with scala repl):

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

But When I wrap this method with another generic method

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

This method always print empty map:

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

The type information seems lost, how to get the printlnMap to print all fields ?


回答1:


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.

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]
}

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))


来源:https://stackoverflow.com/questions/20763434/scala-2-10-2-calling-a-macro-method-with-generic-type-not-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!