Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to change the variance of a base class/trait in Scala?

Tags:

scala

variance

I would like to derive from Scala's immutable Map. It is defined as such:

trait Map[A, +B]

Unfortunately, my implementation needs to be invariant in B. I tried the following, but without success:

def +(kv : (A, B)) : MyMap[A, B] = { ... }

override def +[B1 >: B](kv : (A, B1)) : MyMap[A, B1] =
    throw new IllegalArgumentException()

Maybe there is a trick with @uncheckedVariance?

like image 453
Sebastien Diot Avatar asked Aug 17 '11 18:08

Sebastien Diot


2 Answers

The trouble is that if you derive an invariant version from an immutable map, you'll break type safety. For example:

val dm = DiotMap(1 -> "abc")
val m: Map[Int, Any] = dm

This declaration is valid, because Map is covariant. If your collection cannot handle covariance, what will happen when I use m?

like image 104
Daniel C. Sobral Avatar answered Oct 16 '22 11:10

Daniel C. Sobral


Getting rid of covariance altogether would of course be unsound, and is not allowed. Given m: Map[A, String], and v : Any, you can do val mm : Map[A, Any] = m + v. This is what Map definition says, and all implementors must follow. Your class may be invariant, but it must implement the full covariant interface of Map.

Now redefining + to throw an error is a different story (not very sound yet). The problem with your new + method is that after generics erasure, it has the same signature than the other + method. There is a trick: add in implicit parameter, so that you have two parameters in the signature, which makes it different from the first one.

def +(kv : (A,B))(implicit useless: A <:< A) : MyMap[A,B]

(it doesn't really matter what implicit parameter you're looking for, as long as one is found. implicit useless: Ordering[String] works just as well)

Doing that, you have the usual problem with overloading. If you add a B without the compiler knowing it to be so, the failing method will be called. It might be better to perform a type check there so that B instances are accepted whatever. That would require getting a Manifest[B] in your map.

like image 38
Didier Dupont Avatar answered Oct 16 '22 12:10

Didier Dupont