Scala:具有返回类型的具体实例的实现方法

时间:2013-02-06 13:14:30

标签: scala abstract-type self-type

我需要一种方法来强制抽象类中的方法,使其具有返回类型的被调用对象的具体类。最常见的示例是copy()方法,我目前正在使用基于抽象类型的方法:

abstract class A(id: Int) {
  type Self <: A
  def copy(newId: Int): Self
}

class B(id: Int, x: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

我已经看到了许多方法,包括this great answer中的方法。但是,它们都没有真正强制实现返回自己的类型。例如,以下类是有效的:

class D(id: Int, w: String) extends A(id) {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

我可以这样做的事实导致,如果我正在复制对象的副本,我所拥有的唯一信息是它们属于A的给定子类:

// type error: Seq[A] is not a Seq[CA]!
def createCopies[CA <: A](seq: Seq[CA]): Seq[CA] = seq.map(_.copy(genNewId()))

我能做到更好,更安全的方式吗?

编辑:如果可能的话,我希望能够创建任意深层次的抽象类层次结构。也就是说,在前面的示例中,我希望能够创建扩展A2抽象A,然后继续创建{{1具体的子类。但是,如果这简化了问题(因为它是抽象类型的情况),我不需要进一步扩展已经具体的类。

4 个答案:

答案 0 :(得分:24)

我能想到的唯一解决方案就是这个:

trait CanCopy[T <: CanCopy[T]] { self: T =>
  type Self >: self.type <: T
  def copy(newId: Int): Self
}

abstract class A(id: Int) { self:CanCopy[_] =>
  def copy(newId: Int): Self
}

以下编译:

class B(id: Int, x: String) extends A(id) with CanCopy[B] {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) with CanCopy[C] {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

以下内容无法编译:

class D(id: Int, w: String) extends A(id) with CanCopy[D] {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) with CanCopy[E] {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

修改

我实际上忘了删除复制方法。这可能更通用:

trait StrictSelf[T <: StrictSelf[T]] { self: T =>
  type Self >: self.type <: T
}

abstract class A(id: Int) { self:StrictSelf[_] =>
  def copy(newId:Int):Self
}

答案 1 :(得分:4)

除非在A itelf 的定义中需要绑定,否则不要强制声明方上绑定的类型。以下就足够了:

abstract class A(id: Int) {
  type Self
  def copy(newId: Int): Self
}

现在强制使用网站上的Self类型:

def genNewId(): Int = ???
def createCopies[A1 <: A { type Self = A1 }](seq: Seq[A1]): Seq[A1] = 
  seq.map(_.copy(genNewId()))

答案 2 :(得分:1)

我认为scala不可能做你想做的事。

如果我要:

class Base { type A }
class Other extends Base
class Sub extends Other

现在......我们希望类型A引用“子类的类型。”

你可以从Base的上下文中看到,从编译器的角度来看,特定的“子类类型”的含义并不是特别清楚,从不明白在语法中引用它的语法是什么。家长。在Other中,它意味着Other的实例,但在Sub中,它可能意味着Sub的实例?是否可以定义方法的实现,在Other而不是Other中返回Sub?如果有两个方法返回A,一个在Other中实现,另一个在Sub中实现,这是否意味着Base中定义的类型有两个不同的含义/边界/限制同时?那么如果在这些类之外引用A会发生什么?

我们最接近的是this.type。我不确定理论上是否可以放宽this.type的含义(或提供更宽松的版本),但实现它意味着一个非常特定的类型,所以特定的唯一返回值满足{{ 1}}本身就是def foo:this.type

我希望能够做你的建议,但我不确定它是如何工作的。让我们假设this意味着......更为一般的东西。会是什么?我们不能只说“任何已定义的this.type类型”,因为您不希望this有效。我们可以说“满足所有class Subclass with MyTrait{type A=MyTrait}类型的类型”,但当有人写this时会让人感到困惑......我仍然不确定它是否会以某种方式定义启用上面定义的val a = new Foo with SomeOtherMixinOther的实现。

我们试图混合使用静态和动态定义的类型。

在Scala中,当您说Sub时,class B { type T <: B }特定于实例,而T是静态的(我在java中使用静态方法时使用的那个词) 。您可以说Bclass Foo(o:Object){type T = o.type}对于每个实例都不同....但是当您编写T时,type T=Foo是该类的静态指定类型。您也可以拥有Foo,并提到了一些object BarBar.AnotherType,因为它基本上是“静态的”(虽然在Scala中并不真正称为“静态”),但它不参与AnotherType中的继承。

答案 3 :(得分:0)

  

但是,它们都没有真正强制实现返回自己的类型。例如,以下类是有效的。

但这不正常吗?否则,这意味着您不能仅仅通过示例扩展A来添加新方法,因为它会自动破坏您尝试创建的合同(也就是说,新类copy不会返回此类的实例,但是A)。 能够拥有一个非常精细的课程A的事实,一旦你将其作为课程B延长就会中断,这对我来说是错误的。 但说实话,我无法对其引起的问题说出来。

UPDATE :在考虑了一下这个之后,我认为如果类型检查(“返回类型==最多派生类”)仅在具体类中进行并且从不在抽象类或特征上。 我不知道有任何方法可以在scala类型系统中对其进行编码。

  

我可以这样做的事实导致,如果我正在复制对象的副本,我所拥有的唯一信息是它们是A的给定子类

为什么你不能只返回Seq[Ca#Self]?例如,通过此更改,BcreateCopies的列表会按预期返回Seq[B](而不仅仅是Seq[A]

scala> def createCopies[CA <: A](seq: Seq[CA]): Seq[CA#Self] = seq.map(_.copy(123))
createCopies: [CA <: A](seq: Seq[CA])Seq[CA#Self]

scala> val bs = List[B]( new B(1, "one"), new B(2, "two"))
bs: List[B] = List(B@29b9ab6c, B@5ca554da)

scala> val bs2: Seq[B] = createCopies(bs)
bs2: Seq[B] = List(B@92334e4, B@6665696b)