2

I have two sets, one of them lists instances of a derived class. The set type as created is out of my control, I cannot make sure both are Set[A] instead. The set23 is used in other contexts where I need its type to be Set[B]. I would like to intersect:

case class A(name: String)
class B(name: String) extends A(name)

val set12 = Set(new A("1"), new A("2"))
val set23 = Set(new B("2"), new B("3"))

set12 intersect set23 // does not work

Error is:

type mismatch; found : scala.collection.immutable.Set[B]

required: scala.collection.GenSet[A]

Note: B <: A, but trait GenSet is invariant in type A.

I am aware Set is not covariant, is there some clean workaround without rebuilding the sets? Following works, but I would prefer not to use asInstanceOf:

set12 intersect set23.asInstanceOf[Set[A]]
Suma
  • 33,181
  • 16
  • 123
  • 191

1 Answers1

0

When you create the set as Set(new A("1"), new A("2")), it assignes the type Set[A] to the resulting value. When you create the set as Set(new B("2"), new B("3")) it assignes the type Set[B] to the resulting value.

Therefore, the resulting sets are different types and can not be intersected. But if you initialize your variables as follows:

val set12: Set[A] = Set(new A("1"), new A("2"))
val set23: Set[A] = Set(new B("2"), new B("3"))

then, you are assigning the types yourself. Since the resulting values have the same type, you are able to intersect and do the other operations.

For example, you can use intersect like the following:

val as: Set[A] = set12 intersect set23  // gives empty set as intersection

In the example above, the resulting intersection set is empty since they don't have the same objects.

To achieve successful comparison, you should also override the equals method of classes. In your case, it is enough to override only A. Below is an unrecommended but working way (since extending case classes is not encouraged):

case class A(name: String) {
  override def equals(obj: scala.Any): Boolean = obj match {
    case A(x) => x.equals(name)
    case _ => false
  }
}

class B(name: String) extends A(name)

val set12: Set[A] = Set(new A("1"), new A("2"))
val set23: Set[A] = Set(new B("2"), new B("3"))

val as: Set[A] = set12 intersect set23  // gives A(2) as intersection
println(s"as = ${as}")
fcat
  • 1,251
  • 8
  • 18
  • "But if you initialize your variables as follows:" I tried to write in my question this is something I cannot do. I need the set23 in other context where I need its B type. – Suma Oct 06 '17 at 10:40
  • "resulting intersection set is empty" You are correct, I have oversimplified the code. I have changed A to a case class - I know deriving from a case class is bad practice, but for the purpose of this question this is the shortest way how to make sure equals is overridden. – Suma Oct 06 '17 at 10:45
  • The `case class` example is just for the purpose of the question and it works for this purpose. I am sorry, but by extending how to properly override equals I am no closer to achieving my goal, the core part of the answer is still avoiding the question how to intersect two sets which need to be of different types. – Suma Oct 08 '17 at 10:09
  • After some research on your comment, I think that is not possible to compare two different types even if they have some covariant or contravariant relationship. See [link](https://stackoverflow.com/questions/676615/why-is-scalas-immutable-set-not-covariant-in-its-type) for details. – fcat Oct 08 '17 at 20:34