如何使用 Scala 的 this 类型、抽象类型等来实现 Self 类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4313139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use Scala's this typing, abstract types, etc. to implement a Self type?
提问by Jean-Philippe Pellet
I couldn't find the answer to this in any other question. Suppose that I have an abstract superclass Abstract0 with two subclasses, Concrete1 and Concrete1. I want to be able to define in Abstract0 something like
我无法在任何其他问题中找到答案。假设我有一个抽象超类 Abstract0,它有两个子类 Concrete1 和 Concrete1。我希望能够在 Abstract0 中定义类似的东西
def setOption(...): Self = {...}
where Self would be the concrete subtype. This would allow chaining calls to setOption like this:
其中 Self 将是具体的子类型。这将允许像这样链接对 setOption 的调用:
val obj = new Concrete1.setOption(...).setOption(...)
and still get Concrete1 as the inferred type of obj.
并且仍然得到 Concrete1 作为 obj 的推断类型。
What I don't want is to define this:
我不想定义这个:
abstract class Abstract0[T <: Abstract0[T]]
because it makes it harder for clients to handle this type. I tried various possibilities including an abstract type:
因为它使客户更难处理这种类型。我尝试了各种可能性,包括抽象类型:
abstract class Abstract0 {
type Self <: Abstract0
}
class Concrete1 extends Abstract0 {
type Self = Concrete1
}
but then it is impossible to implement setOption, because thisin Abstract0 does not have type Self. And using this: Self =>also doesn't work in Abstract0.
但是这样就不可能实现 setOption,因为this在 Abstract0 中没有类型 Self。并且this: Self =>在 Abstract0 中使用也不起作用。
What solutions are there to this issue?
这个问题有哪些解决方案?
回答by IttayD
This is what this.typeis for:
这this.type是为了:
scala> abstract class Abstract0 {
| def setOption(j: Int): this.type
| }
defined class Abstract0
scala> class Concrete0 extends Abstract0 {
| var i: Int = 0
| def setOption(j: Int) = {i = j; this}
| }
defined class Concrete0
scala> (new Concrete0).setOption(1).setOption(1)
res72: Concrete0 = Concrete0@a50ea1
As you can see setOption returns the actual type used, not Abstract0. If Concrete0 had setOtherOptionthen (new Concrete0).setOption(1).setOtherOption(...)would work
如您所见,setOption 返回使用的实际类型,而不是 Abstract0。如果 Concrete0 有setOtherOption那么(new Concrete0).setOption(1).setOtherOption(...)会起作用
UPDATE: To answer JPP's followup question in the comment (how to return new instances: The general approach described in the question is the right one (using abstract types). However, the creation of the new instances needs to be explicit for each subclass.
更新:在评论中回答 JPP 的后续问题(如何返回新实例:问题中描述的一般方法是正确的(使用抽象类型)。但是,新实例的创建需要对每个子类都是明确的。
One approach is:
一种方法是:
abstract class Abstract0 {
type Self <: Abstract0
var i = 0
def copy(i: Int) : Self
def setOption(j: Int): Self = copy(j)
}
class Concrete0(i: Int) extends Abstract0 {
type Self = Concrete0
def copy(i: Int) = new Concrete0(i)
}
Another one is to follow the builder pattern used in Scala's collection library. That is, setOption receives an implicit builder parameter. This has the advantages that building the new instance can be done with more methods than just 'copy' and that complex builds can be done. E.g. a setSpecialOption can specify that the return instance must be SpecialConcrete.
另一种是遵循 Scala 集合库中使用的构建器模式。也就是说, setOption 接收一个隐式构建器参数。这样做的优点是可以使用更多方法来构建新实例,而不仅仅是“复制”,并且可以完成复杂的构建。例如,setSpecialOption 可以指定返回实例必须是 SpecialConcrete。
Here's an illustration of the solution:
这是解决方案的说明:
trait Abstract0Builder[To] {
def setOption(j: Int)
def result: To
}
trait CanBuildAbstract0[From, To] {
def apply(from: From): Abstract0Builder[To]
}
abstract class Abstract0 {
type Self <: Abstract0
def self = this.asInstanceOf[Self]
def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = {
val builder = cbf(self)
builder.setOption(j)
builder.result
}
}
class Concrete0(i: Int) extends Abstract0 {
type Self = Concrete0
}
object Concrete0 {
implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] {
def apply(from: Concrete0) = new Abstract0Builder[Concrete0] {
var i = 0
def setOption(j: Int) = i = j
def result = new Concrete0(i)
}
}
}
object Main {
def main(args: Array[String]) {
val c = new Concrete0(0).setOption(1)
println("c is " + c.getClass)
}
}
UPDATE 2: Replying to JPP's second comment. In case of several levels of nesting, use a type parameter instead of type member and make Abstract0 into a trait:
更新 2:回复 JPP 的第二条评论。在多层嵌套的情况下,使用类型参数代替类型成员并使 Abstract0 成为特征:
trait Abstract0[+Self <: Abstract0[_]] {
// ...
}
class Concrete0 extends Abstract0[Concrete0] {
// ....
}
class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] {
// ....
}
回答by pedrofurla
This is the exact use case of this.type. It would be like:
这是this.type. 它会是这样的:
def setOption(...): this.type = {
// Do stuff ...
this
}

