Jea*_*let 44 scala self-type abstract-type
在任何其他问题中我都找不到答案.假设我有一个抽象的超类Abstract0,它有两个子类,Concrete1和Concrete1.我希望能够在Abstract0中定义类似的东西
def setOption(...): Self = {...}
Run Code Online (Sandbox Code Playgroud)
Self将成为具体的子类型.这将允许链接调用setOption,如下所示:
val obj = new Concrete1.setOption(...).setOption(...)
Run Code Online (Sandbox Code Playgroud)
并且仍然将Concrete1作为obj的推断类型.
我不想要的是定义这个:
abstract class Abstract0[T <: Abstract0[T]]
Run Code Online (Sandbox Code Playgroud)
因为它使客户更难以处理这种类型.我尝试了各种可能性,包括抽象类型:
abstract class Abstract0 {
type Self <: Abstract0
}
class Concrete1 extends Abstract0 {
type Self = Concrete1
}
Run Code Online (Sandbox Code Playgroud)
但是后来不可能实现setOption,因为this
在Abstract0中没有类型Self.并且使用this: Self =>
也不适用于Abstract0.
这个问题有什么解决方案?
Itt*_*ayD 59
这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
Run Code Online (Sandbox Code Playgroud)
如您所见,setOption返回使用的实际类型,而不是Abstract0.如果Concrete0 setOtherOption
那么(new Concrete0).setOption(1).setOtherOption(...)
可行
更新:要回答的评论(如何返回新实例JPP的后续问题:在问题中所描述的一般方法是正确的(使用抽象类)但是,新实例的创建必须明确每个子类.
一种方法是:
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)
}
Run Code Online (Sandbox Code Playgroud)
另一个是遵循Scala集合库中使用的构建器模式.也就是说,setOption接收隐式构建器参数.这样做的优势,建设新的实例可以用更多的方法来完成的不仅仅是"复制"和复杂的构建可以做到的.例如,setSpecialOption可以指定返回实例必须是SpecialConcrete.
以下是解决方案的说明:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
更新2:回复JPP的第二条评论.在嵌套的几个层次的情况下,使用类型参数,而不是类型的成员,并作出Abstract0为特征:
trait Abstract0[+Self <: Abstract0[_]] {
// ...
}
class Concrete0 extends Abstract0[Concrete0] {
// ....
}
class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] {
// ....
}
Run Code Online (Sandbox Code Playgroud)
这是的确切用例this.type
。就像:
def setOption(...): this.type = {
// Do stuff ...
this
}
Run Code Online (Sandbox Code Playgroud)