Минимальная платформа в Scala для наборов с наследовавшимся типом возврата

Предположим, что каждый хочет создать новый универсальный класс, 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-always-return-subtypes функцию.)

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

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

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

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

15
задан 0__ 8 June 2013 в 12:07
поделиться

3 ответа

Я не продумал это до конца, но он проверяет тип:

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
}
3
ответ дан 1 December 2019 в 04:57
поделиться

Edit: Я только что понял, что у Рекса был конкретный класс 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. Однако он завершится ошибкой во время выполнения, если вы этого не сделаете и вызовете обратный метод для экземпляра ShortStory.

5
ответ дан 1 December 2019 в 04:57
поделиться

После обсуждений в списке рассылки 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 или фабрику или что-то еще), чтобы оставить абстрактным (здесь я свел все к одному методу, который дублирует конструктор), и вам нужно добавить аннотацию типа MyType , как показано. Затем, на последнем этапе, необходимо создать экземпляры этих методов новой копии.

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

2
ответ дан 1 December 2019 в 04:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: