这是一些示例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
谢谢!
答案 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中没有允许您撰写f
和new A
这样的操作/语句/函数new B
f(new A, new B): A with B
。 (简而言之,不要过于认真地考虑A
和B
是否必须是类或特征或接口等等。
超级电话通常可用于撰写操作。请考虑以下示例:
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
您的问题可以分为两个与组成相关的子问题:
创建所需类型的新实例。这应该通过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)
方法实现。
初始化新创建的对象,使其所有字段具有相同的值(为简洁起见,我们只使用值类型字段,而不是引用类型字段)作为源对象。这应该通过construct
方法实现。
第二个问题可以通过超级电话解决,第一个不能。我们首先考虑更容易的问题(第二个)。
假设initialise
方法按预期工作并产生正确类型的对象。类似于初始示例中construct
方法的组成,我们可以实现op
,以便每个类/特征initialise
,A
和T1
实现{{ 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
或其他什么?通过扩展案例类,
在生成equals
,copy
和toString
等方法时,您会破坏编译器所做的所有假设。
答案 2 :(得分:-1)
最简单和最简单的答案是将具体类型转换为案例类。然后,您将获得编译器提供的copy
方法,该方法接受所有类的构造函数参数的命名参数,以便您可以选择性地将新值与原始值区分开来。