简体   繁体   English

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

[英]Scala: implementing method with return type of concrete instance

I need a way to enforce a method in an abstract class to have a return type of the concrete class of the object it is called on. 我需要一种在抽象类中强制执行方法的方法,使其具有调用它的对象的具体类的返回类型。 The most common example is a copy() method, and I'm currently using an approach based on abstract types: 最常见的示例是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)
}

I already saw many approaches, including the ones in this great answer . 我已经看到了许多方法,包括这个好答案中的方法 However, none of them really forces a implementation to return its own type. 但是,它们都没有真正迫使实现返回自己的类型。 For example, the following classes would be valid: 例如,以下类将有效:

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, "")
}

The fact that I can do that causes that, if I am doing copies of objects of which the only information I have is that they are of a given subclass of A 's: 我可以做到这一点的事实导致,如果我正在复制对象的副本,那么我所拥有的唯一信息就是它们属于A的给定子类:

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

Is there a better, type-safe way I can do that? 有没有更好的,类型安全的方法可以做到这一点?

EDIT: If possible, I would like to keep the ability to create arbitrarily deep hierarchies of abstract classes. 编辑:如果可能的话,我想保持创建抽象类的任意深层次结构的能力。 That is, in the previous example, I'm expecting to be able to create an abstract class A2 that extends A , and then proceed to create A2 's concrete subclasses. 也就是说,在前面的示例中,我期望能够创建扩展A抽象A2 ,然后继续创建A2的具体子类。 However, if that simplifies the problem (as it's the case with abstract types), I do not need to further extend already concrete classes. 但是,如果这简化了问题(抽象类型就是这种情况),则无需进一步扩展已经具体的类。

The only solution I could think of was this one: 我能想到的唯一解决方案是:

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
}

The following would compile: 将编译以下内容:

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)
}

The following would not compile: 以下内容无法编译:

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, "")
}

Edit 编辑

I actually forgot to remove the copy method. 我实际上忘了删除复制方法。 This might be a bit more generic: 这可能更通用:

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

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

Do not force the type bound on the declaration side, unless you need that bound within the definition of A itelf . 除非在A itelf的定义中需要该绑定否则不要在声明方强加该类型绑定。 The following is sufficient: 以下内容就足够了:

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

Now force the Self type on the use site: 现在,在使用站点上强制使用“ Self类型:

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

I don't think it's possible in scala to do what you want. 我认为在Scala中不可能做您想要的事情。

If I were to: 如果我要:

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

Now... we want type A to refer to "the type of the subclass." 现在...我们希望类型A引用“子类的类型”。

You can see that from the context of Base , it's not particularly clear (from the compiler's perspective) what the specific "type of the subclass" means, nevermind what the syntax would be to refer to it in the parent. 您可以从Base的上下文中看到,(从编译器的角度来看)尚不清楚(子类的类型)具体的含义是什么,而不必在父级中引用它的语法。 In Other it would mean an instance of Other , but in Sub it might mean an instance of Sub ? Other那就意味着实例Other ,但在子这可能意味着实例Sub Would it be OK to define an implementation of your method returning an Other in Other but not in Sub ? 可以在Other而不是Sub定义返回Other的方法的实现的定义吗? If there are two methods returning A 's, and one is implemented in Other and the other in Sub , does that mean the type defined in Base has two different meanings/bounds/restrictions at the same time? 如果有两个返回A的方法,一个方法在Other实现,另一个方法在Sub ,这是否意味着Base中定义的类型同时具有两种不同的含义/界限/限制? Then what happens if A is referred to outside of these classes? 那么,如果在这些类之外引用A会发生什么呢?

The closest thing we have is this.type . 我们拥有的最接近的东西是this.type I'm not sure if it would be theoretically possible to relax the meaning of this.type (or provide a more relaxed version), but as implemented it means a very specific type, so specific that the only return value satisfying def foo:this.type is this itself. 我不确定在理论上是否有可能放宽this.type的含义(或提供更宽松的版本),但是在实现时,它意味着一个非常特定的类型,因此必须使唯一的返回值满足def foo:this.type本身就是this

I'd like to be able to do what you suggest, but I'm not sure how it would work. 我希望能够按照您的建议去做,但是我不确定它会如何工作。 Let's imagine that this.type meant... something more general. 让我们想象一下this.type意思是……更通用。 What would it be? 那会是什么? We can't just say "any of the defined types of this ," because you wouldn't want class Subclass with MyTrait{type A=MyTrait} to be valid. 我们不能只说“ this任何定义类型”,因为您不希望class Subclass with MyTrait{type A=MyTrait}有效。 We could say "a type satisfying all of the types of this ," but it gets confusing when someone writes val a = new Foo with SomeOtherMixin ... and I'm still not sure it could be defined in a way that would enable an implementation of both Other and Sub defined above. 我们可以说“满足所有类型的类型this ”,但它变得混乱时,有人写了val a = new Foo with SomeOtherMixin ......,我仍然不知道它可能的方式,可以使一个被定义上面定义的OtherSub实现。

We're sort-of trying to mix static and dynamically defined types. 我们正在尝试混合使用静态和动态定义的类型。

In Scala, when you say class B { type T <: B } , T is specific to the instance, and B is static (I'm using that word in the sense of static methods in java). 在Scala中,当您说class B { type T <: B }T特定于实例,而B是静态的(我在Java中使用静态方法来表示该词)。 You could say class Foo(o:Object){type T = o.type} , and T would be different for every instance.... but when you write type T=Foo , Foo is the statically specified type of the class. 您可以说class Foo(o:Object){type T = o.type} ,每个实例的T都会不同。...但是当您编写type T=FooFoo是该类的静态指定类型。 You could just as well have had an object Bar , and had referred to some Bar.AnotherType . 您可能还拥有一个object Bar ,并且已经引用了Bar.AnotherType The AnotherType , since it's essentially "static," (though not really called "static" in Scala), doesn't participate in inheritance in Foo . AnotherType实质上是“静态的”(尽管在Scala中并未真正称为“静态”),因此它不参与Foo继承。

However, none of them really forces a implementation to return its own type. 但是,它们都没有真正迫使实现返回自己的类型。 For example, the following classes would be valid. 例如,以下类将有效。

But isn't it normal? 但这不正常吗? Otherwise it would mean that you could not merely extend A to add a new method by example, as it would automatically break the contract that you are trying to create (that is, the new class's copy would not return an instance of this class, but of A ). 否则,这意味着您不能仅通过示例扩展A来添加新方法,因为它会自动破坏您尝试创建的协定(也就是说,新类的copy不会返回该类的实例,但是A )。 The very fact of being able to have a perfectly fine class A that breaks as soon as you extend it as class B feels wrong to me. 能够拥有一个完美的AA事实就可以解决,因为B类一旦您将其扩展,就会中断。 But to be honest I have trouble putting words on the problems it causes. 但是说实话,我很难说出它引起的问题。

UPDATE : After thinking a bit more about this, I think this could be sound if the type check ("return type == most-derived class") was made only in concrete classes and never on abstract classes or traits. 更新 :经过更多思考之后,我认为如果仅在具体类中进行类型检查(“返回类型==多数派生的类”),而在抽象类或特性上不进行类型检查,这听起来可能是正确的。 I am not aware of any way to encode that in the scala type system though. 我不知道有什么方法可以在scala类型系统中对其进行编码。

The fact that I can do that causes that, if I am doing copies of objects of which the only information I have is that they are of a given subclass of A's 我可以做到这一点的事实导致,如果我正在复制对象的副本,而该对象的唯一信息是它们属于A的给定子类。

Why can't you just return a Seq[Ca#Self] ? 为什么不只返回Seq[Ca#Self] By example, with this change passing a list of B to createCopies will as expected return a Seq[B] (and not just a Seq[A] : 例如,通过此更改,将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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM