开发者

Generics in a bidirectional association

Let's say I have two classes A and B, with B a subtype of A. This is only part of a richer type hierarchy, obviously, but I don't think that's relevant. Assume A is the root of the hierarchy. There is a collection class C that keeps track of a list of A's. However, I want to make C generic, so that it is possible to make an instance that only keeps B's and won't accept A's.

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)
    }
}

The code above obviously give the error 'type mismatch: found C[B], required C[A]' on the new B(c) line.

I'm not sure how this can be fixed. It's not possible to make C covariant in T (like C[+T <: A]) because the ArrayBuffer is non-variantly typed in T. It's not possible to make the constructor of B require a C[B] because C can't be covariant.

Am I barking up the wrong tree here? I'm a complete Scala newbie, so any ideas and tips might be helpful. Thank you!

EDIT: Basically, what I'd like to have is that the compiler accepts both

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

and

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

but would reject

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

It's probably possible to relax the typing of the ArrayBuffer in C to be A instead of T, and thus in 开发者_运维技巧the addEntry method as well, if that helps.


It's not possible to make C covariant in T (like C[+T <: A]) because the ArrayBuffer is non-variantly typed in T

Not only because of this. The type of addEntry is enough to disallow it:

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

val cb: C[B] = ...

cb.addEntry(b) // works
cb.addEntry(a) // doesn't and shouldn't


Hacky, but seems to work:

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)
    }
}

Of course I would be interested in a proper solution as well...


If you want to keep track of the instances of A, you have to pass an instance of C[A] to the constructor of B since every B is also an A:

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

If however you want to keep track of the Bs then you can't delegate this down to A since A doesn't know anything about B.

Overall, I have the feeling your problem is somewhat ill posed.


Let's say it was possible. Then you'd be able to do this:

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!
        }
    }
}

Trying to run this code will result in a class cast exception.

Edit

You added this requirement:

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

and

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

but would reject

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

However, if B is initialized with C[B], and given that B extends A, then B will initialize A with C[B], thereby violating the last requirement.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜