我正在尝试使用Scala宏来创建单参数copy
方法的案例类映射,每个方法都接受Play Json JsValue
和案例类实例,并返回更新后的副本实例。但是,我遇到了返回函数对象的宏语法问题。
给出一个案例类
case class Clazz(id: Int, str: String, strOpt: Option[String])
目的是创建类的复制方法的地图
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), ...)
我找到了两个相关的问题:
使用宏创建案例类字段映射:Scala Macros: Making a Map out of fields of a class in Scala
使用宏访问案例类复制方法:Howto model named parameters in method invocations with Scala macros?
...但我仍然坚持如何创建一个函数对象,以便我可以返回Map[String, (JsValue, T) => T]
编辑:感谢Eugene Burmako建议使用quasiquotes - 这是我目前正在使用Scala 2.11.0-M7的地方,我的代码基于Jonathan Chow's post(我从使用(T,JsValue)切换= > T to(T,String)=> T以简化我的REPL导入)
Edit2:现在整合$ tpe拼接
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]
答案 0 :(得分:4)
你的代码中几乎所有内容都是正确的,除了你需要将T拼接成一个quasiquote,即写$tpe
而不是T
。
为了看起来更自然,我通常在宏中明确声明类型标记证据,例如def foo[T](c: Context)(implicit T: c.WeakTypeTag[T]) = ...
。之后我只写$T
,看起来几乎没问题:)
你可能会问为什么quququotes不能只知道在它们被写入的地方T
指的是宏的类型参数然后自动将其拼接。这将是非常合理的问题,其实。在诸如Racket和Scheme之类的语言中,quasiquotes足够聪明,可以记住它们所写的词汇上下文,但在Scala中这有点困难,因为语言中有很多不同的范围。然而,有一个计划到那里,并且正在进行这方面的研究:https://groups.google.com/forum/#!topic/scala-language/7h27npd1DKI。