如何使用Scala的这种打字,抽象类型等来实现Self类型?

时间:2010-11-30 11:18:13

标签: scala self-type abstract-type

在任何其他问题上我都找不到答案。假设我有一个抽象的超类Abstract0,它有两个子类,Concrete1和Concrete1。我希望能够在Abstract0中定义像

这样的东西
def setOption(...): Self = {...}

其中Self将成为具体的子类型。这样就可以将对setOption的调用链接到这样:

val obj = new Concrete1.setOption(...).setOption(...)

仍然将Concrete1作为obj的推断类型。

我不想要的是定义:

abstract class Abstract0[T <: Abstract0[T]]

因为它使客户端更难以处理此类型。我尝试了各种可能性,包括抽象类型:

abstract class Abstract0 {
  type Self <: Abstract0
}

class Concrete1 extends Abstract0 {
  type Self = Concrete1
}

但是实现setOption是不可能的,因为Abstract0中的this没有Self类型。使用this: Self =>在Abstract0中也不起作用。

这个问题有什么解决方案?

2 个答案:

答案 0 :(得分: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

如您所见,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)
}

另一个是遵循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)
    }
}

更新2: 回复JPP的第二条评论。如果有多个嵌套级别,请使用类型参数而不是类型成员,并将Abstract0转换为特征:

trait Abstract0[+Self <: Abstract0[_]] {
  // ...
}

class Concrete0 extends Abstract0[Concrete0] {
  // ....
}

class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] {
 // ....
}

答案 1 :(得分:4)

这是this.type的确切用例。这就像是:

def setOption(...): this.type = { 
  // Do stuff ...
  this
}