weakTypeOf
与实例类上绑定的上下文相结合,提供所需的“延迟”类型推断。
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")) // now 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
))
}
}
我们现在可以写foo.withId(2)
代替之前的尝试,foo.withId(foo, 2)
,极其简洁。可能想知道为什么不这样做:foo.copy(id = 2)
?对于具体情况,效果很好,但是当您需要在更抽象的级别应用它时,它根本不起作用。
The following also does not work, seems we must work with concrete case classes instances, so close ;-( For example, let's say you have a DAO and you want to ensure that all updated entities have a valid id. The above macro allows you to do something 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") )
}
由于 Entity 是一个特征而不是一个案例类,如果没有宏,就没有编译时方法来模拟entity.copy(id = id)
.