从scala函数返回一个新的已修改字段对象,该函数的输入参数基于特征

时间:2015-07-14 21:10:01

标签: scala scalaz

我正在尝试使用Slick将特征/类结构建模为通用数据访问层,但是为了限制基本部分,我将发布一个基本代码,该问题具有问题的基本概念。

假设我有一个基本特征,用于定义实体的公共属性。

trait Base {
  def id: Option[Long]
  def version: Long
} 

现在我将根据该特征创建实体。

case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base

一个将Base类型设为泛型的函数。那时我可以创建,更新数据库上的对象而不会出现问题。我的问题是当我想用从数据库返回的生成的id返回原始对象时。

def insert[M <: Base](m: M): M = {
  //insert on database and get the new ID
  val newId = Some(10)
  // Now I want to return the object with the newId
  // As Base is a trait I don't have copy
  m
}

val user = User(None, 1, "Test", 34)
insert(user)

为了说明我想获得一个id = Some(10)的新用户作为插入函数的结果。

我想过使用copy,但如果我用Base case类而不是Trait声明函数,它会起作用,但最初不是我想要的。 我尝试使用Lens,就像scalaz Lens一样。但我也需要复制。

我错过了什么?还有另一种方法可以不使用Reflection吗?

由于

1 个答案:

答案 0 :(得分:3)

您可以使用F绑定多态在要求返回预期类型所需的特征上使用withId方法:

trait Base[Self <: Base[Self]] { self: Self =>
  def id: Option[Long]
  def version: Long
  def withId(id: Long): Self
} 

然后,您可以通过调用其原生withId方法在任何案例类上实现copy

case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base[User] {
  def withId(id: Long) = this.copy(id = Some(id))
}

然后,您可以将insert方法定义为:

def insert[M <: Base[M]](m: M): M = {
  m.withId(10)
}

其余的应该按预期工作。

真正的问题是copy是一种特殊的编译器生成方法,你不能要求它存在于特征上。这种F绑定多态性的使用允许您使用有限的样板解决此限制。

另一种方法是添加另一个特性,比如HasWithId保证withId方法,并在需要的地方扩展/要求:

trait Base {
  def id: Option[Long]
  def version: Long
} 

trait HasWithId[M] {
  def withId(id: Long): M
}

case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base with HasWithId[User] {
  def withId(id: Long): User = this.copy(id = Some(id))
}

def insert[M <: Base with HasWithId[M]](m: M) = {
  m.withId(10)
}