【问题标题】:Case class copy does not maintain state of an inherited trait案例类副本不保持继承特征的状态
【发布时间】:2018-02-15 16:45:33
【问题描述】:

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

    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)

复制:List()

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

【问题讨论】:

  • 没有自动/声明性的方式来处理“不好的函数式编程”

标签: scala


【解决方案1】:

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

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 属性需要通过引用 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)

但同样,这是全局可变状态,这不是一件好事。它不是线程安全的。因此,如果有办法让您在不这样做的情况下找出问题...我鼓励您这样做。

【讨论】:

  • 我没有提到 trait 属性需要通过对 trait 本身的引用来修改,所以不幸的是,这个解决方案再次不起作用。进一步澄清了问题
  • trait 属性需要通过对 trait 本身的引用来修改 你在说的是全局可变状态。在这种情况下,它不能真的在一个特征中,它必须在一个对象中,并且不能从实例访问,而是从A.list =
  • @user79074 看着你更新的问题并猜测你真正想要做什么,我已经更新了答案。
【解决方案2】:

copy 方法是在您声明案例类时由编译器为您生成的,并且仅使用您在案例类中定义的变量进行参数化

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

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

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

【讨论】:

  • 很遗憾,在这种情况下,trait 属性必须是 var。我已经更新了帖子以澄清这一点
  • 如果你没有在 A 中提供实现,你仍然可以在特征 A 和 B 中使用 var。如果你必须同时拥有 var 和默认变量,那么你可能会选择 non-case 类并自己定义一个复制方法
猜你喜欢
  • 2018-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多