案例类副本不维护继承特征的状态

时间:2018-02-15 16:45:34

标签: scala

假设我定义了一个扩展它的特征和案例类:

    trait A {
        var list: List[Int] = List()
        def add(i: Int) = list = i :: list
    }

    case class B(str: String) extends A

如果我然后创建B的实例,修改列表并复制它,副本将获取特征A中定义的列表值:

    val b = B("foo")
    b.add(3)
    println("B: " + b.list)
    println("Copied: " + b.copy(str = "bar").list)

输出:

  

B:列表(3)

     

已复制:列表()

我知道这可能不是一个好的函数式编程实践,但有没有办法可以强制B的副本采用列表的修改版本,例如b?

2 个答案:

答案 0 :(得分:4)

您可以通过声明案例类中的列表来完成此操作,并通过声明方法来满足您的特征类型。所以:

trait A {
    def list: List[Int]
}

case class B(i: Int, var list: List[Int]) extends A

然后你的例子:

scala> val b = B(2, List(1))
b: B = B(2,List(1))

scala>  b.list = List(3)
b.list: List[Int] = List(3)

scala>  println("B: " + b.list)
B: List(3)

scala> println("Copied: " + b.copy(i = 4).list)
Copied: List(3)

我不知道你在做什么,这需要你班上一个可变的列表,但我想尽管这满足了你的例子,它可能无法满足你从未做过的任何问题。 :

scala> val a: A = b
a: A = B(2,List(3))

scala> a.list
res2: List[Int] = List(3)

scala> a.list = List(4)
<console>:15: error: value list_= is not a member of A
       a.list = List(4)

当您尝试将B的实例视为A时,您无法分配列表类型。如果您正在处理A的实例,则必须执行类似

的操作
scala> a match {
     | case itsAB: B => itsAB.list = List(4); itsAB
     | case other => other
     | }
res3: A = B(2,List(4))

但无论如何,这就是你要做的。或者您可以按照phongnt的建议来创建非案例类并自己定义一个复制方法。

在评论中你说:

  

我没有提到trait属性需要通过引用特征本身来修改

但这对我来说听起来像全球可变状态。如果你真的需要,我鼓励你不要然后你可以使用一个对象:

object A {
   @volatile
   var list = List.empty[Int]
}

但是我怀疑这会得到你在你的问题中所表达的内容,因为这里的每个实例都没有单独的列表。所以...看看你更新的问题,我只能猜测这样的事情是你想要完成的事情:

object A {
   @volatile
   var list = List.empty[Int]
}

trait A {
   def list = A.list
   def add(i: Int) = A.list = i :: A.list
}

case class B(str: String) extends A

让你:

scala> val b = B("hi")
b: B = B(hi)

scala> b.add(0)

scala> b.add(1)

scala> A.list
res4: List[Int] = List(1, 0)

但同样,这是全球可变的状态,这不是一件好事。它不是线程安全的。因此,如果您没有这样做就可以找出问题...我鼓励您这样做。

答案 1 :(得分:3)

当您声明一个case类时,编译器会为您生成copy方法,并且它仅使用您在case类中定义的变量进行参数化

因此,您肯定可以通过在案例类中声明变量copy来为您做list方法。 case class B(i: Int, list: List[Int])。您还需要override val修饰符来满足scalac。但是,scalac不允许您覆盖可变变量,因此您也需要更改特征,例如

trait A {
  val list: List[Int] = List(1)
}

case class B(i: Int, override val list: List[Int]) extends A