【问题标题】:How to deep copy classes with traits mixed in如何深度复制混合了特征的类
【发布时间】:2013-02-26 00:52:42
【问题描述】:

这是一些示例 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 traits deep-copy


    【解决方案1】:

    对象构造的组合性

    这里的基本问题是,在 Scala 以及我所知道的所有其他语言中,对象构造不组合。考虑两个抽象操作op1op2,其中op1 使属性 p1 为真,而 op2 使属性 p2 对。如果 op1 ○ op2 使 p1p2 都为真。 (简而言之,属性也需要组合操作,例如等连词。)

    让我们考虑new 操作和new A(): A 的属性,即通过调用new A 创建的对象的类型为Anew 操作缺乏组合性,因为在 Scala 中没有操作/语句/函数f 允许您组合new Anew 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 加一”,设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 通过将它知道的字段设置为实现initialise(objC)来自this 的对应值(单个效果),并通过调用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
    

    是必要的。

    可能的解决方案

    我实现了一个基于抽象类型成员和显式混合类(class AWithT1WithT2 extends A with T1 with T2; val a = new AWithT1WithT2 而不是val a = new A with T1 with T2)的解决方案(see this gist)。它可以工作并且是类型安全的,但它既不是特别好也不简洁。显式 mixin 类是必需的,因为 new A with T1 with T2construct 方法必须能够命名它创建的类型。

    其他类型安全性较低的解决方案可能是可能的,例如,通过asInstanceOf 或反射进行转换。不过,我还没有尝试过类似的方法。

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

    【讨论】:

    • 很好的答案,谢谢!糟糕的是,这是不合法的,输入 self <: super.self with ...>
    【解决方案2】:

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

    【讨论】:

    • 是的,这正是我的想法。将一个类变成一个仅用于复制的案例类是不合逻辑的,这不是它们的用途。如果我只处理一个类,我可以编写一个复制函数,没有问题。我只是不知道如何在混入特征的情况下做到这一点。但我希望有人这样做。
    • 如果你想考虑 trait,你必须在 trait 中实现 copy 方法(这对多个 trait 不起作用)。此外,你应该给它一个 self 类型,而不是扩展 trait,例如:trait T1 { this: A =&gt; },因为从 trait 扩展类具有不同的行为,而不仅仅是限制你可以混入 trait 的类型。
    【解决方案3】:

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

    【讨论】:

    • 虽然这确实复制了具体类,但它不包括混入的特征。
    • 当然可以。 — 哦...您的特质中有状态...尝试将其设计出来。
    • 即使是没有状态的特征,只是一个函数,似乎也无法为我复制。案例类 A(val x: Int); trait T extends A {def say=println(x)} 请原谅格式
    猜你喜欢
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 1970-01-01
    • 2021-04-03
    • 2018-10-13
    • 2012-05-09
    相关资源
    最近更新 更多