Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
614 views
in Technique[技术] by (71.8m points)

reflection - How to use scala macros to create a function object (to create a Map[String, (T) => T])

I am trying to use Scala macros to create a case class map of single-parameter copy methods, with each method accepting a Play Json JsValue and a case class instance, and returning an updated copy of the instance. However, I am running into problems with the macro syntax for returning a function object.

Given a case class

case class Clazz(id: Int, str: String, strOpt: Option[String])

the intention is to create a map of the class's copy methods

implicit def jsonToInt(json: JsValue) = json.as[Int]
implicit def jsonToStr(json: JsValue) = json.as[String]
implicit def jsonToStrOpt(json: JsValue) = json.asOpt[String]

Map("id" -> (json: JsValue, clazz: Clazz) = clazz.copy(id = json),
  "str" -> (json: JsValue, clazz: Clazz) = clazz.copy(str = json), ...)

I have found two related questions:

Using macros to create a case class field map: Scala Macros: Making a Map out of fields of a class in Scala

Accessing the case class copy method using a macro: Howto model named parameters in method invocations with Scala macros?

...but I am stuck at how I can create a function object so that I can return a Map[String, (JsValue, T) => T]


Edit: Thanks to Eugene Burmako's suggestion to use quasiquotes - this is where I'm currently at using Scala 2.11.0-M7, basing my code on Jonathan Chow's post (I switched from using (T, JsValue) => T to (T, String) => T to simplify my REPL imports)

Edit2: Now incorporating $tpe splicing

import scala.language.experimental.macros

implicit def strToInt(str: String) = str.toInt

def copyMapImpl[T: c.WeakTypeTag](c: scala.reflect.macros.Context): 
    c.Expr[Map[String, (T, String) => T]] = {

  import c.universe._

  val tpe = weakTypeOf[T]

  val fields = tpe.declarations.collectFirst {
    case m: MethodSymbol if m.isPrimaryConstructor => m
  }.get.paramss.head

  val methods = fields.map { field => {
    val name = field.name
    val decoded = name.decoded
    q"{$decoded -> {(t: $tpe, str: String) => t.copy($name = str)}}"
  }}

  c.Expr[Map[Sring, (T, String) => T]] {
    q"Map(..$methods)"
  }
}

def copyMap[T]: Map[String, (T, String) => T] = macro copyMapImpl[T]

case class Clazz(i: Int, s: String)

copyMap[Clazz]
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You got almost everything right in your code, except for the fact that you need to splice T into a quasiquote, i.e. to write $tpe instead of just T.

For that to look more natural, I usually explicitly declare type tag evidences in macros, e.g. def foo[T](c: Context)(implicit T: c.WeakTypeTag[T]) = .... After that I just write $T, and it looks almost fine :)

You might ask why quasiquotes can't just figure out that in the place where they're written T refers to the type parameter of a macro and then automatically splice it in. That would be very reasonable question, actually. In languages like Racket and Scheme, quasiquotes are smart enough to remember things about the lexical context they are written in, but in Scala this is a bit more difficult, because there are so many different scopes in the language. Yet, there's a plan to get there, and research in that direction in already underway: https://groups.google.com/forum/#!topic/scala-language/7h27npd1DKI.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...