Минимальный каркас в Scala для коллекций с наследуемым типом возврата - PullRequest
15 голосов
/ 09 июня 2010

Предположим, что кто-то хочет создать новый родовой класс, Novel[A].Этот класс будет содержать много полезных методов - возможно, это тип коллекции - и поэтому вы хотите создать его подкласс.Но вы хотите, чтобы методы возвращали тип подкласса, а не исходный тип.В Scala 2.8, какой минимальный объем работы нужно выполнить, чтобы методы этого класса возвращали соответствующий подкласс, а не оригинал?Например,

class Novel[A] /* What goes here? */ {
  /* Must you have stuff here? */
  def reverse/* What goes here instead of :Novel[A]? */ = //...
  def revrev/*?*/ = reverse.reverse
}
class ShortStory[A] extends Novel[A] /* What goes here? */ {
  override def reverse: /*?*/ = //...
}
val ss = new ShortStory[String]
val ss2 = ss.revrev  // Type had better be ShortStory[String], not Novel[String]

Меняется ли эта минимальная сумма, если вы хотите, чтобы Novel был ковариантным?

(коллекции 2.8 делают это среди прочего, но они также играют с типами возвратав более причудливых (и полезных) способах - вопрос в том, как мало фреймворка можно избежать, если нужно только свойство subtypes-всегда-return-subtypes.)

Edit: в приведенном выше коде предполагается, чтоreverse делает копию.Если кто-то выполняет модификацию на месте, а затем возвращает себя, он может использовать this.type, но это не работает, потому что копия не является this.

Арджаном, связанным с другим вопросом, который предлагает следующее решение:

def reverse: this.type = {
  /*creation of new object*/.asInstanceOf[this.type]
}

, которая в основном принадлежит системе типов, чтобы получить то, что мы хотим.Но на самом деле это не решение, потому что теперь, когда мы обманули систему типов, компилятор не может помочь нам убедиться, что мы действительно do получаем ShortStory назад, когда думаем, что мыделать.(Например, нам не пришлось бы переопределять reverse в приведенном выше примере, чтобы сделать компилятор счастливым, но наши типы не были бы тем, что мы хотели.)

Ответы [ 3 ]

5 голосов
/ 09 июня 2010

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

Возможно, не самый красивый, но простой пример с использованием абстрактных типов элементов может быть реализован следующим образом:


trait Novel[A] { 
   type T <: Novel[A] 
   def reverse : T 
   def revrev : T#T = reverse.reverse 
}

class ShortStory[A](var story: String) extends Novel[A] {
 type T = ShortStory[A]
 def reverse : T = new ShortStory[A](story reverse)
 def myMethod: Unit = println("a short story method")
}

scala> val ss1 = new ShortStory[String]("the story so far")
ss1: ShortStory[String] = ShortStory@5debf305

scala> val ssRev = ss1 reverse 
ssRev: ss1.T = ShortStory@5ae9581b

scala> ssRev story
res0: String = raf os yrots eht

scala> val ssRevRev = ss1 revrev
ssRevRev: ss1.T#T = ShortStory@2429de03

scala> ssRevRev story
res1: String = the story so far

scala> ssRevRev myMethod
a short story method

Это, конечно, минимально, но я сомневаюсь, достаточно ли этого для использования в качестве своего рода фреймворка. И, конечно же, типы возвращаются не так ясно, как в структуре коллекций Scala, поэтому, возможно, это будет слишком просто. Однако для данного случая это, похоже, делает работу. Как отмечалось выше, это не делает работу для данного случая, поэтому здесь требуется другое решение.

Еще одно редактирование: Нечто подобное можно сделать и с помощью конкретного класса, хотя этого также недостаточно для обеспечения безопасности типов:


class Novel[A](var story: String) {
  type T <: Novel[A] 
  def reverse: T = new Novel[A](story reverse).asInstanceOf[T]  
  def revrev : T#T = reverse.reverse
}
class ShortStory[A](var s: String) extends Novel[A](s) {
 type T = ShortStory[A]
 override def reverse : T = new ShortStory(story reverse)
 def myMethod: Unit = println("a short story method")
}

И код будет работать как в примере с чертой. Но это страдает от той же проблемы, что и Рекс, упомянутый в его редактировании. Переопределение в ShortStory не является необходимым для этой компиляции. Тем не менее он завершится с ошибкой во время выполнения, если вы не сделаете этого и не вызовете метод reverse для экземпляра ShortStory.

3 голосов
/ 10 июня 2010

Я не продумал это полностью, но он проверяет тип:

object invariant {
  trait Novel[A] {
    type Repr[X] <: Novel[X]

    def reverse: Repr[A]

    def revrev: Repr[A]#Repr[A]
       = reverse.reverse
  }
  class ShortStory[A] extends Novel[A] {
    type Repr[X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}

object covariant {
  trait Novel[+A] {
    type Repr[X] <: Novel[_ <: X]

    def reverse: Repr[_ <: A]

    def revrev: Repr[_ <: A]#Repr[_ <: A] = reverse.reverse
  }

  class ShortStory[+A] extends Novel[A] {
    type Repr[X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}

EDIT

Ко-вариантная версия может быть намного приятнее:

object covariant2 {
  trait Novel[+A] {
    type Repr[+X] <: Novel[X]

    def reverse: Repr[A]

    def revrev: Repr[A]#Repr[A] = reverse.reverse
  }

  class ShortStory[+A] extends Novel[A] {
    type Repr[+X] = ShortStory[X]

    def reverse = this
  }

  val ss = new ShortStory[String]
  val ss2: ShortStory[String] = ss.revrev
}
2 голосов
/ 16 июня 2010

После обсуждений в списке рассылки Scala - большое спасибо людям за то, что они поставили меня на правильный путь! - Я думаю, что это самое близкое, что можно прийти к минимальным рамкам. Я оставляю это здесь для справки, и я использую другой пример, потому что он подчеркивает, что происходит лучше:

abstract class Peano[A,MyType <: Peano[A,MyType]](a: A, f: A=>A) {
  self: MyType =>
  def newPeano(a: A, f: A=>A): MyType
  def succ: MyType = newPeano(f(a),f)
  def count(n: Int): MyType = {
    if (n<1) this
    else if (n==1) succ
    else count(n-1).succ
  }
  def value = a
}

abstract class Peano2[A,MyType <: Peano2[A,MyType]](a: A, f: A=>A, g: A=>A) extends Peano[A,MyType](a,f) {
  self: MyType =>
  def newPeano2(a: A, f: A=>A, g: A=>A): MyType
  def newPeano(a: A, f: A=>A): MyType = newPeano2(a,f,g)
  def pred: MyType = newPeano2(g(a),f,g)
  def uncount(n: Int): MyType = {
    if (n < 1) this
    else if (n==1) pred
    else uncount(n-1).pred
  }
}

Ключевым моментом здесь является добавление параметра типа MyType, который является заполнителем для типа класса, с которым мы действительно закончим. Каждый раз, когда мы наследуем, мы должны переопределять его как параметр типа, и мы должны добавить метод конструктора, который создаст новый объект этого типа. Если конструктор изменится, мы должны создать новый метод конструктора.

Теперь, когда вы хотите создать класс для фактического использования, вам нужно только заполнить метод конструктора вызовом new (и сообщить классу, что он имеет свой собственный тип):

class Peano2Impl[A](a: A, f: A=>A, g: A=>A) extends Peano2[A,Peano2Impl[A]](a,f,g) {
  def newPeano2(a: A, f: A=>A, g: A=>A) = new Peano2Impl[A](a,f,g)
}

и все готово:

val p = new Peano2Impl(0L , (x:Long)=>x+1 , (y:Long)=>x-1)

scala> p.succ.value
res0: Long = 1

scala> p.pred.value
res1: Long = -1

scala> p.count(15).uncount(7).value
res2: Long = 8

Итак, чтобы рассмотреть, минимальный шаблон - если вы хотите включить рекурсивные методы, которые нарушают другой стиль ответа - предназначен для любых методов, которые возвращают новую копию извне класса (используя new или factory или что угодно), чтобы оставить его абстрактным (здесь я свел все к одному методу, который дублирует конструктор), и вы должны добавить аннотацию типа MyType, как показано. Затем, на последнем шаге, необходимо создать новые методы копирования.

Эта стратегия прекрасно работает и для ковариации в A, за исключением того, что этот конкретный пример не работает, поскольку f и g не являются ковариантными.

...