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

时间:2013-02-26 00:52:42

标签: scala traits deep-copy

这是一些示例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应该返回一个带有T1的T2的新B,并且q1.copy应该返回一个新的C,其中T1带有T2

谢谢!

3 个答案:

答案 0 :(得分:3)

对象构造的组合性

这里的基本问题是,在Scala以及我所知道的所有其他语言中,对象构造不会撰写。考虑两个抽象操作 op 1 op 2 ,其中 op 1 使属性 p 1 为true,其中 op 2 使属性 p 2 是的。如果 op 1 ○op 2 <,这些操作对于组合操作○可组合的 / em>使 p 1 p 2 为true。 (简化,属性还需要合成操作,例如等合并。)

让我们考虑new操作以及new A(): A的属性,即通过调用new A创建的对象的类型为A的属性。 new操作缺少组合性,因为Scala中没有允许您撰写fnew A这样的操作/语句/函数new B 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增加1”,让T.op的属性“y的长度增加1”。通过超级呼叫实现的组合实现了两种属性。 Hooooray!

您的问题

假设您正在使用的字段A,其字段为x,特征T1具有字段y和另一个特征{{1} }有一个字段T2。你想要的是以下几点:

z

您的问题可以分为两个与组成相关的子问题:

  1. 创建所需类型的新实例。这应该通过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) 方法实现。

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

  3. 第二个问题可以通过超级电话解决,第一个不能。我们首先考虑更容易的问题(第二个)。

    对象初始化

    假设initialise方法按预期工作并产生正确类型的对象。类似于初始示例中construct方法的组成,我们可以实现op,以便每个类/特征initialiseAT1实现{{ 1}}通过将其知道的字段设置为T2(单个效果)中的相应值,并通过调用initialise(objC)来组合这些单独的效果。

    对象创建

    据我所知,没有办法构建对象。如果要创建this的实例,则必须在某处执行语句super.initialise(objC)。如果超级电话可以在这里帮助,那么像

    A with T1 with T2

    是必要的。

    可能的解决方案

    我基于抽象类型成员和显式mixin类(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 )实现了一个解决方案(see this gist)。它有效并且它是类型安全的,但它既不特别好也不简洁。显式mixin类是必需的,因为class AWithT1WithT2 extends A with T1 with T2; val a = new AWithT1WithT2的{​​{1}}方法必须能够命名它创建的类型。

    其他类型安全性较低的解决方案可能是可行的,例如,通过val a = new A with T1 with T2或反射进行投射。不过,我没有尝试过这些方法。

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

答案 1 :(得分:2)

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

答案 2 :(得分:-1)

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