Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some questions about covariance in Scala

I'm trying to understand covariance in Scala, but I cant find any examples that help me with this problem. I' ve got this code:

    class GenericCellImm[+T] (val x: T) {}

and it compile well, but when I use it

    class GenericCellMut[+T] (var x: T) { }

it doesn't compile. Why I can't use var (but I can use val) when I'm writing this code? How can I fix it? Also here is similar situation

    abstract class Sequence[+A] {
    def append(x: Sequence[A]): Sequence[A]} 

What is the problem?

like image 524
alqueen Avatar asked Jan 09 '19 11:01

alqueen


2 Answers

An apple is a fruit.

An immutable bag of apples is an immutable bag of fruit. A bag of apples contains apples, which are fruit; you can get an apple out of such a bag (well, not really, because it's immutable, but you can retrieve a copy of an apple) and end up with a fruit in your hands. No problem with that.

But a mutable bag of apples is not a mutable bag of fruit, because you can put stuff in a mutable bag of fruit. Like a banana, for instance. A bag of apples is only allowed to contain apples, not bananas!

And this is exactly the reason why Scala doesn't allow the first construct.

What about the second one? Scala allows you to do this, with some modifications. But the result is contravariant. In effect, you can create X[Fruit] which is a kind of X[Apple]. X is a bit like a bag, but it works in the opposite direction. What X can be if we stick to the fruit analogy? Think of it as a juicer. You can put apples in an apple juicer. You can put any kind of fruit in a fruit juicer. Somewhat paradoxically, a fruit juicer is a kind of apple juicer! The only thing an apple juicer can do is squeezing apples. But a fruit juicer can do that as well, which is the basis of the is-a-kind-of (subtyping) relationship.

like image 144
n. 1.8e9-where's-my-share m. Avatar answered Sep 28 '22 15:09

n. 1.8e9-where's-my-share m.


You can't write def append(x: Sequence[A]): Sequence[A]}, as A is in contravariant position in the append argument, while being covariant.

You should write it like this:

abstract class Sequence[+A] {
   def append[B >: A](x: Sequence[B]): Sequence[B]
}

You have a great explanation in Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

In short:

+A states that it is safe to convert this A to a supertype of A in all context ( A Dog maybe converted to an Animal). If you write append[A](x: Sequence[A]) you are stating that x can only be A or subtypes of A (Dog, yorsay etc), but never a supertype (like Animal), so this contradicts the +A annotation and fails at compilation time. With the signature append[B >: A](x: Sequence[B]) you fix it by avoiding naming A in the function arguments.

So, [B >: A] is defining a lower bound for B, stating that B must be a supertype of A, or A, but never any class below A in the hierarchy, and hence complying with +A signature.

I know covariance and contravariance are complex concepts and hard to understand, I also get confused from time to time.

like image 36
Alejandro Alcalde Avatar answered Sep 28 '22 16:09

Alejandro Alcalde