Idea Transcript
#defines
Map[String, Any]
trait Mappable[T] { def toMap(t: T): Map[String, Any] def fromMap(map: Map[String, Any]): T }
Mappable[T]
T
case class Person(name: String, age: Int) val PersonMapper = new Mappable[Person] { def toMap(p: Person) = Map( "name" -> p.name, "age" -> p.age) def fromMap(map: Map[String, Any]) = Person( map("name").asInstanceOf[String] map("age").asInstanceOf[Int]) }
Person
case class Person(name: String, age: Int, height: Double) val PersonMapper = new Mappable[Person] { // toMap: compiles even though it's incorrect def toMap(p: Person) = Map( "name" -> p.name, "age" -> p.age) // fromMap: fails to compile (as it should) def fromMap(map: Map[String, Any]) = Person( map("name").asInstanceOf[String] map("age").asInstanceOf[Int]) }
fromMap toMap
height
Mappable
import scala.reflect.macros.Context object Mappable { implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T] def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = { import c.universe._ val tpe = weakTypeOf[T] c.Expr[Mappable[T]] { q""" new Mappable[$tpe] { def toMap(t: $tpe) = ??? def fromMap(map: Map[String, Any]) = ??? } """ } }
implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]
Mappable
macro materializeMappableImpl
def personToMap(p: Person) = { val mapper = materializeMappable[Person] mapper.toMap(p) }
Mapper[T]
// the compiler will insert materializeMappable[T] as the implicit parameter def mapify[T](t: T)(implicit mapper: Mappable[T]) = mapper.toMap(t)
def mapify[T: Mappable](t: T) = implicitly[Mappable[T]].toMap(t)
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = { import c.universe._ // _... }
val tpe = weakTypeOf[T] c.Expr[Mappable[T]] { q""" new Mappable[$tpe] { def toMap(t: $tpe) = ??? def fromMap(map: Map[String, Any]) = ??? } """ }
WeakTypeTag
reify
splice
$variable reify
Expr Expr
Expr
Mappable[T] tpe
T
??? Mappable
Mapper
isCaseAccessor
fromMap
tpe declarations members
val declarations = tpe.declarations val ctor = declarations.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor => m }.get val params = ctor.paramss.head
paramss
head
toMap
def toMap(p: Person) = Map( "name" -> p.name, "age" -> p.age)
Map.apply
String ->
val toMapParams = fields.map { field => val name = field.name val mapKey: String = name.decoded q"$mapKey -> t.$name" }
mapKey
String
String
Liftable
Literal(Constant(mapKey))
$op_names
contenttype
content$minustype t t t
toMap toMap
t
t
toMap
c.Expr[Mappable[T]] { q""" new Mappable[$tpe] { def toMap(t: $tpe) = Map(..$toMapParams) def fromMap(map: Map[String, Any]) = ??? } """ }
toMap
t ..$toMapParams List[T]
...
List[List[T]]
fromMap
Mappable
toMap
def mapify[T: Mappable](t: T) = implicitly[Mappable[T]].toMap(t) case class Item(name: String, price: Double) val map = mapify(Item("lunch", 15.5)) println(map("name")) // "lunch" println(map("price")) // 15.5
fromMap
def fromMap(map: Map[String, Any]) = Person( map("name").asInstanceOf[String] map("age").asInstanceOf[Int])
toMap
apply tpe
val companion = tpe.typeSymbol.companionSymbol def returnType(name: Name) = tpe.declaration(name).typeSignature
toMap fromMap
val fromMapParams = fields.map { field => val name = field.name val decoded = name.decoded val returnType = tpe.declaration(name).typeSignature q"map($decoded).asInstanceOf[$returnType]" } c.Expr[Mappable[T]] { q""" new Mappable[$tpe] { def toMap(t: $tpe) = Map(..$toMapParams) def fromMap(map: Map[String, Any]) = $companion(..$fromMapParams) } """ }
String map
fromMap apply
apply
def materialize[T: Mappable](map: Map[String, Any]) = implicitly[Mappable[T]].fromMap(map) case class Item(name: String, price: Double) val item = materialize[Item](Map("name" -> "dinner", "price" -> 25.8)) println(item.name) // "dinner" println(item.price) // 25.8
complete-example
import scala.reflect.macros.Context trait Mappable[T] { def toMap(t: T): Map[String, Any] def fromMap(map: Map[String, Any]): T } object Mappable { implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T] def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = { import c.universe._ val tpe = weakTypeOf[T] val companion = tpe.typeSymbol.companionSymbol val fields = tpe.declarations.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor Þ m }.get.paramss.head val (toMapParams, fromMapParams) = fields.map { field Þ val name = field.name val decoded = name.decoded val returnType = tpe.declaration(name).typeSignature (q"$decoded Õ t.$name", q"map($decoded).asInstanceOf[$returnType]") }.unzip c.Expr[Mappable[T]] { q""" new Mappable[$tpe] { def toMap(t: $tpe): Map[String, Any] = Map(..$toMapParams) def fromMap(map: Map[String, Any]): $tpe = $companion(..$fromMapParams) } """ } } }