Как вернуть правильный тип из переопределенного метода в Scala?

Будучи новичком в Scala, я наткнулся на этот, казалось бы, простой момент методов подкласса и переопределения.

Я специализировал Set таким образом:

    class SpecializedSet [T](s:Int, b: Boolean) (implicit ordering: Ordering [T]) extends TreeSet [T] { 
      override def + (t: T): SpecializedSet [T] = {

           if (this.isEmpty) {

              s = s + 1

              // I want to add an element to the Set
              super.+ (t) 

           }
           ....
       }

На месте использования этого класса я делаю:

       class Driver {

         var e = new SpecializedSet [MyEarlierDefinedType](3,false);

         ......

         val elem1 = new MyEarlierDefinedType()

         e = e + eleme1 

         ......
     }

Компилятор сразу жалуется:

несоответствие типов; найдено: scala.collection.immutable.TreeSet[T] требуется: org.personal.exercises.SpecializedSet[T]

Я понимаю, что переопределенный метод «+» должен возвращать тип «SpecializedSet» — подтип — и простой вызов super.+() не достигает этого.

Это не тот же TreeSet, который возвращает super.+(), это новый TreeSet, созданный на его месте. Я думаю, что теперь я должен сам создать новый экземпляр SpecializedSet(), используя этот новый TreeSet. Я застрял здесь. Как создать новый SpecializedSet(), используя TreeSet, который является его супертипом? Какую идиому в мире Scala использовать в таких случаях? Является ли использование asInstanceOf() наиболее подходящим и коротким ответом здесь? Но разве использование этого метода не обескураживает все это время?

Должен ли я создавать сопутствующий объект SpecializedSet, определяя в нем метод apply()? В качестве альтернативы мне нужно пойти намного глубже и использовать концепцию Traits, описанную в Scala: как написать метод, который возвращает объект, типизированный для типа реализации получателя и Подклассы и возвращаемые типы и другие связанные ссылки? Или следуйте более сложному направлению создания Builder, как в http://www.scala-lang.org/docu/files/collections-api/collections-impl.html.?

Я также рассмотрел этот вопрос (и ответы): Расширение коллекции Scala - и они безусловно полезно, но почему-то мне кажется, что это нечто большее, чем я понял. Например, одно из решений в этой ссылке — явно указать тип базового класса в сигнатуре функции, например:

      override def + (t: T): TreeSet [T] = { // instead of SpecializedSet 
             ......

Но тогда разве это не нарушение ожиданий вызывающего метода? Я смущен.

Должно ли решение быть настолько сложным, как указано в этих ссылках? Каков очевидный момент, который я упускаю? Любой указатель будет полезен. Я провел довольно тщательный поиск, но если мой вопрос повторяется, пожалуйста, потерпите меня и направьте меня.


person Nirmalya    schedule 04.02.2013    source источник
comment
Вы должны использовать ... extends TreeSet [T] с SortedSetLike[T, SpecializedSet[T]]   -  person Eastsun    schedule 04.02.2013
comment
Вероятно, вам не следует пытаться расширять коллекции, пока вы не освоитесь со Scala. Если хотите, да, посмотрите примеры в scala-lang.org/docu/files/collections-api/collections-impl.html. В противном случае стандартные методы сбора не будут работать с вашей новой коллекцией или будут иметь неожиданное поведение.   -  person Alexey Romanov    schedule 04.02.2013


Ответы (1)


Я бы предпочел расширить соответствующий трейт и использовать класс через композицию вместо прямого наследования:

class SpecializedSet[T](s: Int, b: Boolean)(implicit ordering: Ordering[T]) extends SortedSet[T] {
  private var ts = new TreeSet[T]()
  override def +(t: T): SpecializedSet[T] = {
    ts += t
    this
  }
  override def -(t: T): SpecializedSet[T] = {
    ts -= t
    this
  }
  override def contains(t: T): Boolean = ts.contains(t)
  override def iterator(): Iterator[T] = ts.iterator
  override def ordering(): Ordering[T] = ts.ordering
  override def rangeImpl(from: Option[T], until: Option[T]) = ts.rangeImpl(from, until)
}

Я думаю, что этот подход более удобен для новичков, поскольку он позволяет избежать прямого наследования классов и (прямых) вызовов super. Недостатком этого очень простого решения является то, что мы ввели изменчивость. Эту проблему можно решить, немного изменив дизайн конструктора, чтобы можно было сделать член val и действительно вернуть новый измененный экземпляр SpecializedSet вместо this.

person bluenote10    schedule 04.02.2013
comment
Это соответствует моему мышлению и обеспечивает простое решение. Действительно, для новичка, такого как я, это должно быть подходом. Спасибо @ bluenote10. - person Nirmalya; 04.02.2013