36

我需要一种方法来强制抽象类中的方法具有调用它的对象的具体类的返回类型。最常见的例子是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)
}

我已经看到了很多方法,包括这个很好的答案中的方法。但是,它们都没有真正强制实现返回自己的类型。例如,以下类将是有效的:

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's 子类:

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

有没有更好的、类型安全的方法可以做到这一点?

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

4

4 回答 4

26

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

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
}
于 2013-02-16T12:30:29.773 回答
4

不要强制在声明端绑定类型,除非您需要在Aitelf的定义中绑定。以下内容就足够了:

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()))
于 2013-02-16T00:44:44.547 回答
1

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

如果我要:

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

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

您可以从 的上下文中看到,Base并不是特别清楚(从编译器的角度来看)特定的“子类的类型”是什么意思,更不用说在父类中引用它的语法是什么了。在Other它意味着一个实例Other,但在 Sub 它可能意味着一个实例Sub?定义一个返回OtherinOther但不是 in的方法的实现可以Sub吗?如果有两种方法返回A's,一个在 in 中实现,Other另一个在 in 中实现Sub,这是否意味着 Base 中定义的类型同时具有两种不同的含义/界限/限制?那么如果A在这些类之外引用会发生什么?

我们拥有的最接近的是this.type. 我不确定理论上是否可以放宽this.type(或提供更宽松的版本)的含义,但在实现时它意味着一个非常具体的类型,如此具体以至于唯一满足的返回值def foo:this.type就是this它自己。

我希望能够按照您的建议进行操作,但我不确定它会如何工作。让我们想象一下,这this.type意味着……更笼统的东西。那会是什么?我们不能只说“任何定义的类型” this,因为您不想class Subclass with MyTrait{type A=MyTrait}成为有效的。我们可以说“一个满足所有类型的类型” this,但是当有人写的时候它会变得混乱val a = new Foo with SomeOtherMixin......我仍然不确定它是否可以以一种能够实现上面定义的方式来Other定义Sub

我们有点尝试混合静态和动态定义的类型。

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

于 2013-02-16T16:00:10.097 回答
0

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

但这不正常吗?否则,这将意味着您不能仅A通过示例扩展以添加新方法,因为它会自动破坏您尝试创建的协定(即,新类copy不会返回此类的实例,而是返回A) . 能够拥有一个完美的班级A,一旦你扩展它,因为班级B对我来说是错误的,这一事实就被打破了。但老实说,我很难说出它引起的问题。

更新:在考虑了更多之后,我认为如果类型检查(“返回类型 == 最派生类”)仅在具体类中进行,而不是在抽象类或特征上进行,这可能是合理的。不过,我不知道有任何方法可以在 scala 类型系统中对其进行编码。

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

为什么你不能只返回一个Seq[Ca#Self]?例如,通过此更改传递Bto列表createCopies将按预期返回 a Seq[B](而不仅仅是 a 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)
于 2013-02-06T14:47:43.537 回答