PepeHands
PepeHands

Reputation: 1406

Scala: const method in covariant class

Suppose I want to implement some sort of immutable set in scala and I want it to have 2 methods -- add and contains. Also it seems logical to me that a Set[Cat] is a set of Set[Animals] (thus I want it to be declated as MySet[+A]).

So I write something like this

class MySet[+A] {
    def add[B >: A](elem: B): MySet[B]
}

Which seems to be ok until I try to implement contains method. In my opinion, the best fitting signature for a method like it should be def contains(elem: A): Boolean because a set of Cats can only contain Cats. It can not contain Dogs or any other animals.

Of course scala complains about the method like this: Covariant type A occurs in a contravariant position in type A of value elem and I even understand why.

My question is how to convince scala that my method contains does not mutate my collection in any way and it is safe to pass an element of type A into it.

Upvotes: 1

Views: 87

Answers (2)

chengpohi
chengpohi

Reputation: 14217

If for typesafe contains, you can try to create the typesafe implicits for contains to solve Set's contains method parameter must be cotravariant problem, like:

  //implicit bound for `MySet` with the type `A`, so in there `A` is invariant type. 
  implicit class TypeSafeMySetContains[A](s: MySet[A]) {
    def has(a: A): Boolean = s.contains(a) // invoke the `contains` method check
  }
  MySet(1, 2, 3) has "3" // the compiler will throw type mismatch error
  MySet(1, 2, 3) has 3

Upvotes: 1

Dima
Dima

Reputation: 40500

The problem is not that scala is afraid you are going to mutate the collection.

The problem is that, because your set is covariant, MySet[Cat] is a subclass of MySet[Animal]. So, if the latter is allowed to have def contains(a: Animal), the former must have one too.

You can make it work with the same trick you did with .add:

 def contains[B >: A](elem: B): Boolean

Note that the immutable Set in standard scala library is actually invariant in the element type. That is because they wanted it to work as a function A => Boolean, that would require it to have an apply(a: A) that's essentially the same problem you are talking about.

And give back my nick BTW, this is not cool, man!

Upvotes: 2

Related Questions