scala macros: defer type inference -
preamble: based on @travis brown's macro based solution copying case class properties.
given:
trait entity[e <: entity[e]]{self:e=> def id: int def withid(id: int) = macrocopy.withid(self,id) } case class user(id: int, name: string) extends entity[user] and macro implementation:
object macrocopy { import scala.language.experimental.macros import scala.reflect.macros.blackbox.context def withid[t](entity: t, id: int): t = macro withidimpl[t] def withidimpl[t: c.weaktypetag] (c: context)(entity: c.expr[t], id: c.expr[int]): c.expr[t] = { import c.universe._ val tree = reify(entity.splice).tree val copy = entity.actualtype.member(termname("copy")) val params = copy match { case s: methodsymbol if (s.paramlists.nonempty) => s.paramlists.head case _ => c.abort(c.enclosingposition, "no eligible copy method!") } c.expr[t](apply( select(tree, copy), assignornamedarg(ident(termname("id")), reify(id.splice).tree) :: nil )) } } is there way somehow defer type inference in such way macro operates on user , not entity's self type? stands type checker knows nothing user's case class copy method since sees value of type e.
i'd do:
val u = user(2,"foo") u.withid(3) most of alternative solutions i've seen entail defining withid abstract in entity trait , implementing method in every case class, prefer avoid if possible.
weaktypeof combined context bound on instance's class provides desired "delayed" type inference.
trait entity[e <: entity[e]]{self:e=> def id: int def withid(id: int) = macrocopy.withidimpl[e] } case class user(id: int, name: string) extends entity[user] object macrocopy { import scala.language.experimental.macros import scala.reflect.macros.blackbox.context def withidimpl[t <: entity[t]: c.weaktypetag] // context bound on entity (c: context)(id: c.expr[int]): c.expr[t] = { import c.universe._ val tree = reify( c.expr[t](c.prefix.tree).splice ).tree val copy = weaktypeof[t].member(termname("copy")) // lookup case class' copy method val params = copy match { case s: methodsymbol if (s.paramlists.nonempty) => s.paramlists.head case _ => c.abort(c.enclosingposition, "no eligible copy method!") } c.expr[t](apply( select(tree, copy), assignornamedarg(ident(termname("id")), reify(id.splice).tree) :: nil )) } } we can write foo.withid(2) in place of previous attempt, foo.withid(foo, 2), wonderfully concise. might wondering why not do: foo.copy(id = 2)? concrete case works fine, when need apply @ more abstract level, works not @ all.
the following not work, seems must work concrete case classes instances, close ;-( for example, let's have dao , want ensure updated entities have valid id. above macro allows like:
def update[t <: entity[t]](entity: t, id: int)(implicit ss: session): either[string,unit] = { either( byid(id).mutate(_.row = entity.withid(id)), i18n("not updated") ) } since entity trait , not case class, without macro there no compile-time way of simulating entity.copy(id = id). workaround have redefined dao update method follows:
def update[t <: entity[t]](fn: id => t, id: int)(implicit ss: session): either[string,unit] = { either( byid(id).mutate(_.row = fn(id)), i18n("not updated") ) } that @ least forces 1 supply u.withid(_:int) function update method. better having potentially invalid entities @ runtime, still not elegant performing withid right before matters (i.e. persisting db), thereby avoiding mule work (boilerplate) of passing concrete function instance update, sigh, there must way pull off macros.
in other news, having written first macro, liking potential here, awesome stuff ;-)
Comments
Post a Comment