2

I have a curious problem, maybe because my classes' structure is a bit complex, but anyway :

So first I have 2 abstract classes : TestAbstract1 and TestAbstract2.

  • TestAbstract2 takes a type extending TestAbstract1
  • TestAbstract1 declares a val named valTest of type TestAbstract2[TestAbstract1] that has to be implemented in child classes

Code :

abstract class TestAbstract1 {
    val valTest: TestAbstract2[TestAbstract1]

    def meth1(): List[TestAbstract1] = {
        valTest.meth2()
    }
}

abstract class TestAbstract2[T <: TestAbstract1] {
    def meth2(): List[T] = {
        List()
    }
}

Then I have one object TestObject2 that extends TestAbstract2, and a basic class Test2 that extends TestAbstract1, and has to implement valTest :

class Test2 extends TestAbstract1 {
    val valTest: TestAbstract2[Test2] = TestObject2
}

object TestObject2 extends TestAbstract2[Test2] { }

The problem is here : when I compile, it tells me :

[error] overriding value valTest in class TestAbstract1 of type models.test.TestAbstract2[models.test.TestAbstract1];

[error] value valTest has incompatible type

[error] val valTest: TestAbstract2[Test2] = TestObject2

I don't know what I am doing wrong, because if I think about polymorphism rules, it should be alright ...

Do you have any idea ? Or maybe even a better way of doing what I want ?

Thank you !

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Zarkus13
  • 302
  • 2
  • 9

1 Answers1

6

In your example, TestAbstract2 is not covariant. Meaning that even if we have

Test2 <: TestAbstract1

It is not the case that:

TestAbstract2[Test2] <: TestAbstract2[TestAbstract1]

Have a look here if this does not make sense to you.

In your example, valTest as declared in Test2 is of type TestAbstract2[Test2] but expected to be TestAbstract2[TestAbstract1], therefore the error.

You have the following options:

  1. Declare TestAbstract2 as covariant:

    class TestAbstract2[+T <: TestAbstract1]
    
  2. Declare valTest using wildchard types:

    val valTest: TestAbstract2[_ <: TestAbstract1]
    
  3. Parametrize TestAbstract1 on the type of the inner TestAbstract2:

    class TestAbstract1[T <: TestAbstract1[T]] {
      val valTest: TestAbstract2[T]
      // ...
    }
    

    Changing Test2 to:

    class Test2 extends TestAbstract1[Test2]
    

Note that the choice of using F-bounded polymorphism (bounding T by a function of itself in TestAbstract1) in the third example is somewhat arbitrary here. I just needed some type to put in for the sake of the example, and in your example it works (when looking at the definition of Test2). Which of these three versions is the best for you depends on how you want to use these classes.

If this is not sufficient for you, please provide more detail in your questions and we'll gladly help.

Community
  • 1
  • 1
gzm0
  • 14,752
  • 1
  • 36
  • 64
  • Thank you very much, it's working like a charm ! I declared TestAbstract2 as covariant, but in fact, TestAbstract2 extends an other type parameterized class (yes, one more), and I had to use @uncheckedVariance to this declaration. But it's working as expected ;) Again : thank you ! – Zarkus13 Jun 29 '13 at 15:07
  • @Zarkus13 Glad it worked out for you. Take care with using `@uncheckedVariance`! It might allow you to use methods in a way which is not typesafe. (See the link in my answer) – gzm0 Jul 01 '13 at 16:11