web-dev-qa-db-ja.com

Scalaのthis型、抽象型などを使用してSelf型を実装する方法は?

他の質問でこれに対する答えを見つけることができませんでした。 2つのサブクラス、Concrete1とConcrete1を持つ抽象スーパークラスAbstract0があるとします。 Abstract0で次のように定義できるようにしたい

def setOption(...): Self = {...}

ここで、Selfは具体的なサブタイプです。これにより、次のようにsetOptionの呼び出しをチェーンできます。

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

そして、推定されたタイプのobjとしてConcrete1を取得する。

私が望まないのはこれを定義することです:

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

クライアントがこのタイプを処理することが難しくなるためです。抽象型を含むさまざまな可能性を試しました:

abstract class Abstract0 {
  type Self <: Abstract0
}

class Concrete1 extends Abstract0 {
  type Self = Concrete1
}

ただし、Abstract0のthisにはタイプSelfがないため、setOptionを実装することは不可能です。そしてthis: Self =>もAbstract0では機能しません。

この問題にはどのような解決策がありますか?

44

これは何 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のフォローアップの質問に回答するには(新しいインスタンスを返す方法:質問で説明されている一般的なアプローチは正しい(抽象型を使用する)ものです。ただし、新しいインスタンスの作成は、サブクラスごとに明示的である必要があります。

1つのアプローチは次のとおりです。

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

もう1つは、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の2番目のコメントに返信します。ネストのレベルが複数ある場合は、型メンバーの代わりに型パラメーターを使用して、Abstract0を特性にします。

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

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

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

これは、this.typeの正確な使用例です。それは次のようになります:

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