Как использовать эту типизацию Scala, абстрактные типы и т. Д. Для реализации типа Self? - PullRequest
44 голосов
/ 30 ноября 2010

Я не мог найти ответ на этот вопрос ни в одном другом вопросе.Предположим, что у меня есть абстрактный суперкласс 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, потому что this в Abstract0 не имеет типа Self.И использование this: Self => также не работает в Abstract0.

Какие есть решения для этой проблемы?

Ответы [ 2 ]

59 голосов
/ 30 ноября 2010

Вот для чего 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] {
 // ....
}
4 голосов
/ 30 ноября 2010

Это точный вариант использования this.type.Это было бы как:

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