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]
JsValue
and sort of append that to an existing instance ofClazz
?. ie (conceptually):Json.obj("id" -> newId) ++ instance
should equalinstance.copy(id = newId)
– driangleClazz
instance from the database (Slick on top of MySql), decomposes it to json, updates the json, converts it back to an updatedClazz
instances, and updates the database with the new instance. The reason we're exploring macros as a way to create aMap[String, CopyFun]
is that we'd like to decouple the json processing from the database layer – Zim-Zam O'Pootertoot