scala宏:推迟类型推断

时间:2014-09-09 19:58:47

标签: scala macros inference

序言:这是基于@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定义为抽象,然后在每个案例类中实现该方法,如果可能的话,更愿意避免这种情况。

1 个答案:

答案 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类,没有宏,就没有模拟entity.copy(id = id)的编译时方法。作为一种解决方法我有重新定义了DAO更新方法如下:

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(样板)更新,叹息,必须有一个用宏来解决这个问题。

在其他新闻中,编写了我的第一个宏,我真的很喜欢这里的潜力,很棒的东西; - )