如何深度复制混合了特征的类



下面是一些scala代码示例。

abstract class A(val x: Any) {  
    abstract def copy(): A  
}  
class b(i: Int) extends A(i) {  
    override def copy() = new B(x)  
}  
class C(s: String) extends A(s) {  
    override def copy() = new C(x)  
}  
//here's the tricky part  
Trait t1 extends A {  
    var printCount = 0  
    def print = {  
        printCount = printCount + 1  
        println(x)  
    }  
    override def copy = ???  
}  
Trait t2 extends A {  
    var doubleCount = 0  
    def doubleIt = {  
        doubleCount = doubleCount + 1  
        x = x+x  
    }  
    override def copy = ???  
}  
val q1 = new C with T1 with T2  
val q2 = new B with T2 with T1  

好吧,正如你可能已经猜到的,这是一个问题。我如何在T1和T2中实现复制方法,这样,如果它们与B、C或T2/T1混合,我就会得到整个蜡球的副本?例如,q2.copy应该返回一个带有T2和T1的新B,q1.copy应该返回带有T1和T2 的新C

谢谢!

对象构造的组成

这里的基本问题是,在Scala和我所知道的所有其他语言中,对象构造都不构成。考虑两个抽象运算op1op2,其中op1[/em>使属性p1为true,其中op2使属性p2为true。对于组合运算,这些运算是可组合的,如果操作1○op2使p1p2之类的连词。)

让我们考虑new操作和new A(): A的属性,即通过调用new A创建的对象的类型为Anew操作缺少组合性,因为Scala中没有允许您组合new Anew B的操作/语句/函数f,使得f(new A, new B): A with B。(简单地说,不要太在意AB是否必须是类、特性或接口或其他什么)。

使用超级调用进行合成

超级调用通常可以用于组合操作。考虑以下示例:

abstract class A { def op() {} }
class X extends A {
  var x: Int = 0
  override def op() { x += 1 }
}
trait T extends A {
  var y: String = "y"
  override def op() { super.op(); y += "y" }
}
val xt = new X with T
println(s"${xt.x}, ${xt.y}") // 0, y
xt.op()
println(s"${xt.x}, ${xt.y}") // 1, yy

X.op的性质为"x加一",设T.op的性质为y的长度加一。通过超级调用实现的组合同时满足这两个特性。万岁!

你的问题

假设您正在处理一个类A,它有一个字段x,一个特征T1,它有字段y,另一个特征T2,它有域z。您想要的是以下内容:

val obj: A with T1 with T2
// update obj's fields
val objC: A with T1 with T2 = obj.copy()
assert(obj.x == objC.x && obj.y == objC.y && obj.z == objC.z)

你的问题可以分为两个与合成性相关的子问题:

  1. 创建所需类型的新实例。这应该通过construct方法来实现。

  2. 初始化新创建的对象,使其所有字段都具有与源对象相同的值(为简洁起见,我们将只处理值类型的字段,而不是引用类型的字段)。这应该通过initialise方法来实现。

第二个问题可以通过超级调用来解决,而第一个问题则不能。我们先考虑比较容易的问题(第二个)。

对象初始化

让我们假设construct方法按需工作,并生成正确类型的对象。类似于初始示例中op方法的组成,我们可以实现initialise,使得每个类别/特征AT1T2通过将其所知道的字段设置为来自this(个体效果)的相应值来实现initialise(objC),并通过调用super.initialise(objC)来组成这些个体效果。

对象创建

就我所见,没有办法组合对象创建。如果要创建A with T1 with T2的实例,那么语句new A with T1 with T2必须在某个地方执行。如果超级呼叫可以帮助这里,那么像这样的东西

val a: A = new A // corresponds to the "deepest" super-call
val at1: A with T1 = a with new T1

将是必要的。

可能的解决方案

我实现了一个基于抽象类型成员和显式mixin类(class AWithT1WithT2 extends A with T1 with T2; val a = new AWithT1WithT2而不是val a = new A with T1 with T2)的解决方案(请参阅要点)。它是有效的,是类型安全的,但它既不是特别好,也不是特别简洁。显式mixin类是必要的,因为new A with T1 with T2construct方法必须能够命名它创建的类型。

其他类型不太安全的解决方案可能是可行的,例如,通过asInstanceOf或反射进行投射。不过,我还没有尝试过类似的东西。

Scala宏可能也是一种选择,但我还没有使用过它们,因此对它们的了解还不够。编译器插件可能是另一个重磅选项。

这就是为什么不赞成扩展案例类的原因之一。你应该得到一个编译器的警告。A中定义的复制方法应该如何知道也可能存在T或其他什么?通过扩展case类,您打破了编译器在生成equalscopytoString等方法时所做的所有假设。

最简单的答案是将您的具体类型转换为case类。然后获得编译器提供的copy方法,该方法接受类的所有构造函数参数的命名参数,这样您就可以选择性地区分新值和原始值。

最新更新