Дженерики в двунаправленной ассоциации - PullRequest
1 голос
/ 08 января 2011

Допустим, у меня есть два класса A и B, с B подтипом A. Очевидно, что это только часть иерархии более богатых типов, но я не думаю, что это актуально.Предположим, что A является корнем иерархии.Существует класс коллекции C, который отслеживает список A.Тем не менее, я хочу сделать C универсальным, чтобы можно было создать экземпляр, который хранит только B и не принимает A.

class A(val c: C[A]) {
    c.addEntry(this)
}
class B(c: C[A]) extends A(c)
class C[T <: A]{
    val entries = new ArrayBuffer[T]()
    def addEntry(e: T) { entries += e }
}
object Generic {
    def main(args : Array[String]) {
        val c = new C[B]()
        new B(c)
    }
}

Приведенный выше код, очевидно, дает ошибку «несоответствие типов»: найденоC [B], требуется C [A] 'в строке new B(c).

Я не уверен, как это можно исправить.Невозможно сделать C ковариантным в T (например, C[+T <: A]), потому что ArrayBuffer не типизирован в T. Невозможно заставить конструктор B требовать C [B], потому что C не может быть ковариантным.

Я лаю не на том дереве?Я полный новичок в Scala, поэтому любые идеи и советы могут быть полезны.Спасибо!

РЕДАКТИРОВАТЬ: В основном, я хотел бы, чтобы компилятор принимал как

val c = new C[B]()
new B(c)

и

val c = new C[A]()
new B(c)

, но отклонил бы

val c = new C[B]()
new A(c)

Возможно, можно смягчить тип ArrayBuffer в C, чтобы он был A вместо T, и, таким образом, в методе addEntry, если это поможет.

Ответы [ 4 ]

1 голос
/ 08 января 2011

Невозможно сделать C-ковариантным в T (как C[+T <: A]), потому что ArrayBuffer не типизирован для T

Не только из-за этого. Тип addEntry достаточно, чтобы запретить его:

val a: A = ...
val b: B = ...

val cb: C[B] = ...

cb.addEntry(b) // works
cb.addEntry(a) // doesn't and shouldn't
0 голосов
/ 10 января 2011

Допустим, это было возможно. Тогда вы сможете сделать это:

class A(val c: C[A]) {
    c.addEntry(this)
}
class B(c: C[A]) extends A(c)
class C[+T <: A]{
    val entries: ArrayBuffer[T] @uncheckedVariance = new ArrayBuffer[T]()
    def addEntry(e: T @uncheckedVariance) { entries += e }
}
object Generic {
    def main(args : Array[String]) {
        // Everything's fine so far...
        val c = new C[B]()
        c.addEntry(new B(c))
        // but, suddenly...
        val ca: C[A] = c
        ca.addEntry(new A(ca))
        // a problem appears!
        c.entries forall {
            case thing: B => true // ok
            case otherThing => false // not ok -- c now contains an A!
        }
    }
}

Попытка запустить этот код приведет к исключению приведения класса.

Редактировать

Вы добавили это требование:

val c = new C[B]()
new B(c)

и

val c = new C[A]()
new B(c)

но отклонил бы

val c = new C[B]()
new A(c)

Однако, если B инициализируется с C[B], и если B расширяется A, то B инициализирует A с C[B], нарушая тем самым последнее требование.

0 голосов
/ 08 января 2011

Если вы хотите отслеживать экземпляры A, вы должны передать экземпляр C [A] в конструктор B, поскольку каждый B также является A:

def main(args : Array[String]) {
    val c = new C[A]()
    new B(c)
}

Если, однако, вы хотите отслеживать B, то вы не можете передать это А, поскольку A ничего не знает о B.

В целом, я чувствую, что ваша проблема несколько плохо поставлена.

0 голосов
/ 08 января 2011

Хаки, но, кажется, работает:

class A(val c: C[A]) {
  c.addEntry(this.asInstanceOf[c.X])
}

class B(c: C[B]) extends A(c)

class C[+T <: A] {
    type X <: T
    val entries = new ArrayBuffer[X]()
    def addEntry(e: X) { entries += e }
}

object Generic {
    def main(args : Array[String]) {
        val c = new C(){ type T = B }
        new B(c)
    }
}

Конечно, я бы тоже заинтересовался правильным решением ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...