序言:这是基于@Travis Brown的macro based solution来复制案例类属性。
假设:
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]
和宏实现:
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
))
}
}
有没有办法以某种方式推迟类型推断,使得宏在User
而不是实体的自我类型上运行?就目前而言,类型检查器对User
的案例类复制方法一无所知,因为它看到的只是E
类型的值。
我想这样做:
val u = User(2,"foo")
u.withId(3)
我见过的大多数替代解决方案都需要在实体特征中将withId定义为抽象,然后在每个案例类中实现该方法,如果可能的话,更愿意避免这种情况。
答案 0 :(得分:0)
weakTypeOf
结合实例的类上下文提供了所需的&#34;延迟&#34;类型推断。
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)
?对于工作正常的具体案例,但是当你需要在更抽象的层面上应用它时,它根本不起作用。
以下也不起作用,似乎我们必须使用具体案例类实例,所以关闭;-( 例如,让我们说你有一个DAO并且你想确保所有更新的实体有一个有效的ID。上面的宏允许你做类似的事情:
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是一个trait而不是case类,没有宏,就没有模拟作为一种解决方法我有重新定义了DAO更新方法如下:entity.copy(id = id)
的编译时方法。
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") )
}
至少强制一个人为更新方法提供u.withId(_:Int)函数。比在运行时有可能无效的实体更好,但仍然不如在它重要之前执行withId那么优雅(即持久化到DB),从而避免传递具体函数实例的mule work(样板)更新,叹息,必须有一个用宏来解决这个问题。
在其他新闻中,编写了我的第一个宏,我真的很喜欢这里的潜力,很棒的东西; - )