0

I'm trying to compile the following code, but the last line does not compile:

class SuperContainer (
  val shapeSets: Set[MyContainer[Shape]] = Set.empty[MyContainer[Shape]]) {

  def addAct(el: MyContainer[Shape]) = {
    new SuperContainer(shapeSets + el)
  }

}

class MyContainer[A](val ls: Set[A] = Set.empty[A]) {
  def addElement(el: A) = {
    new MyContainer(ls + el)
  }
}  


abstract class Shape

case class Circle(radius: Int) extends Shape {
  override def toString = "Circle(" + radius + ")"
}
case class Square(s: Int) extends Shape {
  override def toString = "Square(" + s + ")"
}


object MyContainer {
  def main(args: Array[String]) {
    //Circle Container
    val myc1 = new MyContainer[Circle]()
    val myc11 = myc1.addElement(new Circle(6))

    //Square Container
    val myc2 = new MyContainer[Square]()
    val myc21 = myc2.addElement(new Square(6))


    val scont = new SuperContainer
    scont.addAct(myc11) //does not compile
  }
}

Scala compiler suggests me to use +A in MyContainer class definition, but by doing that, other compile errors occur. Am I doing something wrong or this is just a Scala limitation? Is there any way to overcome this problem?

kwstas
  • 147
  • 6
  • 1
    I don't understand what you are trying to achieve: your add* functions don't return anything (you're missing the equals sign) and don't store the modified data anywhere either. – danielkza Nov 19 '13 at 00:05
  • You're right. It was just some typo. I corrected the code. I also now store the modified data. – kwstas Nov 19 '13 at 11:17
  • The compiler is right, your SuperContainer is invariant, and it should be covariant. What are the other errors that show up when you make the correction? – danielkza Nov 19 '13 at 11:35
  • When i use `class MyContainer[+A](val ls: Set[A] = Set.empty[A])` I get 2 errors. In the class definition: _covariant type A occurs in invariant position in type => Set[A] of value ls_ and in method `addElement(el: A)`: _covariant type A occurs in contravariant position in type A of value el_ – kwstas Nov 19 '13 at 11:57

1 Answers1

3

In order to achieve what you want, MyContainer has to be covariant:

class MyContainer[+A](val ls: Set[A] = Set.empty[A]) // ...

Now, your definition of addElement will cause an error, since A appears in contravariant position (as a function argument in this case). You will have to adapt your signature as follows:

def addElement[B >: A](el: B): MyContainer[B]

This makes sense if you think of it: If you have a Container[Circle] (which can be seen as a Container[Shape] due to covariance) and you add a Shape, you have a Container[Shape] at the end and not a Container[Circle].

The implementation of addElement will not change.

Further, you cannot make Set[A] available outside the class (i.e. you have to remove the val), since Set[A] is not covariant. If you want to access elements, you'll have to add additional methods to query the set.

class MyContainer[+A](ls: Set[A] = Set.empty[A]) // ...

UPDATE

This is to explain clearer, why Set[A] cannot be part of MyContainer[+A]'s public API. Say we have:

class A
class B extends A

Imagine the following:

val x: MyContainer[A] = new MyContainer[B]

We can do that due to the covariance. However, if we could call now:

val s = x.ls // get internal set

We expect s to be of type Set[A]. However, the internal Set of x is a Set[B] which is not a Set[A]. Therefore this is not correctly typed.

Community
  • 1
  • 1
gzm0
  • 14,752
  • 1
  • 36
  • 64
  • I understood the most part of your answer, apart from the last sentence. Do you mean that I should remove the Set[A] from the constructor? By doing what you suggested, now `addElement` seems to compile, but i get an error about the Set[A]: _covariant type A occurs in invariant position in type => Set[A] of value ls_ – kwstas Nov 19 '13 at 13:51
  • @kwstas Have a look at the updated answer. You cannot expose the `Set` in `MyContainer`'s public API, since it is invariant. – gzm0 Nov 19 '13 at 14:18
  • thanks a lot. It worked fine, even though I can't really understand why (I'm little bit confused with covariance etc). Why we cannot expose the Set to the public API? Is it because we don't know it's type? – kwstas Nov 19 '13 at 15:11
  • Thanks again for your explanation. So the problem is that a set is invariant, but if I change the set with a list, I can expose it in the API, because Lists are covariant. I think I got it know :) – kwstas Nov 20 '13 at 09:55