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

Scala:使用具体实例的返回类型实现方法,scala,abstract-type,self-type,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(new

我需要一种在抽象类中强制执行方法的方法,以使其具有所调用对象的具体类的返回类型。最常见的例子是
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)
}
我可以这样做的事实导致,如果我复制的对象的唯一信息是它们属于
a
的给定子类,那么:

// type error: Seq[A] is not a Seq[CA]!
def createCopies[CA <: A](seq: Seq[CA]): Seq[CA] = seq.map(_.copy(genNewId()))
//类型错误:Seq[A]不是Seq[CA]!
def createCopies[CA
但是,它们都没有真正强制实现返回自己的类型

但这不是很正常吗?否则,这意味着您不能仅仅通过示例扩展
A
来添加一个新方法,因为它会自动破坏您试图创建的契约(即,新类的
副本
不会返回该类的实例,而是
A
)。 我觉得能够拥有一个完美的类
a
,当你将它扩展为类
B
时它就会崩溃,这一事实本身就是错误的。 但老实说,我很难用语言来描述它所造成的问题

更新:在仔细考虑之后,我认为如果类型检查(“返回类型==most派生类”)只在具体类中进行,而不是在抽象类或特征上进行,那么这可能是合理的。 不过,我不知道在scala类型的系统中如何对其进行编码

事实上,我可以这样做,如果我做的是对象的副本,我所拥有的唯一信息是它们是a的给定子类

为什么不能只返回一个
Seq[Ca#Self]
?举例来说,通过将
B
列表传递给
createCopies
将按预期返回一个
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)
scala>def createCopies[CA val bs2:Seq[B]=createCopies(bs)
bs2:Seq[B]=列表(B@92334e4, B@6665696b)

不要强制在声明端绑定类型,除非您需要在
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()))
def genNewId():Int=???

def createCopies[A1我能想到的唯一解决方案是:

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 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, "")
}
编辑 实际上我忘了删除copy方法。这可能更一般一些:

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

abstract class A(id: Int) { self:StrictSelf[_] =>
  def copy(newId:Int):Self
}
trait-StrictSelf[T
键入Self>:Self.type
def副本(newId:Int):Self
}

我认为在scala中不可能实现您想要的功能

如果我要:

class Base { type A }
class Other extends Base
class Sub extends Other
现在…我们希望类型A引用“子类的类型”

您可以从
Base
的上下文中看到,(从编译器的角度)不太清楚具体的“子类类型”是什么表示,无论在父级中引用它的语法是什么。在
Other
中,它表示
Other
的实例,但在Sub中,它可能表示
Sub
的实例。如果有两种方法返回
A
,一种在
Other
中实现,另一种在
Sub
中实现,这是否意味着Base中定义的类型同时具有两种不同的含义/界限/限制?如果
A
在这些类之外引用,会发生什么情况

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

我很想按照你的建议去做,但我不确定它是如何工作的。让我们想象一下,
this.type
意味着…更一般的东西。它会是什么?我们不能只说“this
的任何定义类型”,因为你不会想要带有MyTrait{type A=MyTrait}的
类子类
是有效的。我们可以说“一个类型满足了
这个
的所有类型”,但是当有人用另一个mixin写
val a=new Foo时,它会让人感到困惑,我仍然不确定它的定义方式是否能够实现上面定义的
Other
Sub

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


在Scala中,当你说
class B{type T时,你已经指出了
this.type
的情况。为什么它不适合你?
this.type
是一个实例特定的类型(即两个不同
A
实例的
this.type
不相同),因此不适合我的
copy()
method。我想提出类似的问题:如何约定定义类的任何子类都应该关注一些方法,因为
final
新来者也可以阅读tpolat关于主题的文章。出于好奇,为什么不将其设为构造函数呢?每个子类都必须提供自己的
副本
方法(也就是说,在具体类实现它之前,它将是一个抽象方法),因为只有具体的子类才知道对象“副本”的返回类型。在我看来,这不像是强制要求;事实上,Java是在Cloneable()中实现的,但以非类型安全的方式,并且如果该方法未被重写,则使用反射。
CA#Self
不一定是
CA
;因此,我无法访问复制实例中的
CA
特定方法和字段。我看不到
Cloneable
的示例是如何相关的,因为它确实不相关(也无法)强制子类重写Clone并返回inst
trait StrictSelf[T <: StrictSelf[T]] { self: T =>
  type Self >: self.type <: T
}

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