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

Popular posts from this blog

javascript - RequestAnimationFrame not working when exiting fullscreen switching space on Safari -

jsf - How to ajax update an item in the footer of a PrimeFaces dataTable? -

django - CSRF verification failed. Request aborted. CSRF cookie not set -