2

I am new in Scala world and now I am reading the book called "Scala in Action" (by Nilanjan Raychaudhuri), namely the part called "Mutable object need to be invariant" on page 97 and I don't understand the following part which is taken directly from the mentioned book.


Assume ListBuffer is covariant and the following code snippet works without any compilation problem:

scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)

Can you spot the problem? Because everything is of the type Any, you can store an integer value into a collection of strings. This is a disaster waiting to happen. To avoid these kinds of problems, it’s always a good idea to make mutable objects invariant.


I would have the following questions..

1) What type of everything is in reality? String or Any? The declaration is "val everything: ListBuffer[Any]" and hence I would expect Any and because everything should be type of Any then I don't see any problems to have Integer and String in one ListBuffer[Any]. How can I store integer value into collection of strings how they write??? Why disaster??? Why should I use List (which is immutable) instead of ListBuffer (which is mutable)? I see no difference. I found a lot of answers that mutably collections should have type invariant and that immutable collections should have covariant type but why?

2) What does the last part "res4: everything.type = ListBuffer(1, pants)" mean? What does "everything.type" mean? I guess that everything does not have any method/function or variable called type.. Why is there no ListBuffer[Any] or ListBuffer[String]?

Thanks a lot,

Andrew

Andrew_256
  • 229
  • 2
  • 6
  • Hi! If I have time later, I can attempt to write a full answer (unless someone beats me to it or closes the question as duplicate). But in the meantime, sounds like [this article](https://medium.com/@sinisalouc/variance-in-java-and-scala-63af925d21dc) could help you with some concepts. – slouc Mar 21 '18 at 16:09

2 Answers2

5

1 This doesn't look like a single question, so I have to subdivide it further:

  1. "In reality" everything is ListBuffer[_], with erased parameter type. Depending on the JVM, it holds either 32 or 64 bit references to some objects. The types ListBuffer[String] and ListBuffer[Any] is what the compiler knows about it at compile time. If it "knows" two contradictory things, then it's obviously very bad.
  2. "I don't see any problems to have Integer and String in one ListBuffer[Any]". There is no problem to have Int and String in ListBuffer[Any], because ListBuffer is invariant. However, in your hypothetical example, ListBuffer is covariant, so you are storing an Int in a ListBuffer[String]. If someone later gets an Int from a ListBuffer[String], and tries to interpret it as String, then it's obviously very bad.

  3. "How can I store integer value into collection of strings how they write?" Why would you want to do something that is obviously very bad, as explained above?

  4. "Why disaster???" It wouldn't be a major disaster. Java has been living with covariant arrays forever. It's does not lead to cataclysms, it's just bad and annoying.

  5. "Why should I use List (which is immutable) instead of ListBuffer (which is mutable)?" There is no absolute imperative that tells you to always use List and to never use ListBuffer. Use both when it is appropriate. In 99.999% of cases, List is of course more appropriate, because you use Lists to represent data way more often than you design complicated algorithms that require local mutable state of a ListBuffer.

  6. "I found a lot of answers that mutably collections should have type invariant and that immutable collections should have covariant type but why?". This is wrong, you are over-simplifying. For example, intensional immutable sets should be neither covariant, nor invariant, but contravariant. You should use covariance, contravariance, and invariance when it's appropriate. This little silly illustration has proven unreasonably effective for explaining the difference, maybe you too find it useful.

2 This is a singleton type, just like in the following example:

scala> val x = "hello"
x: String = hello

scala> val y: x.type = x
y: x.type = hello

Here is a longer discussion about the motivation for this.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
0

I agree with most of what @Andrey is saying I would just add that covariance and contravariance belong exclusively to immutable structures, the exercisce that the books proposes is just a example so people can understand but it is not possible to implement a mutable structure that is covariant, you won't be able to make it compile. As an exercise you could try to implement a MutableList[+A], you'll find out that there is not way to do this without tricking the compiler putting asInstanceOf everywhere

Mikel San Vicente
  • 3,831
  • 2
  • 21
  • 39
  • Saying that it is "not possible to implement a mutable structure that is covariant" is again an over-simplification. Does for example a mutable tuple that can hold two values `a1` and `a2` of same type `A`, and has a mutating method `swap(): Unit` count as a "mutable structure"? It certainly is a data structure (not a very useful one, but still), and it is certainly mutable (even though it has only `swap`, no setters). There would be no problem implementing it as covariant. This whole "immutable = covariant" "mutable = invariant" story is not a rule, it's a vague heuristic at best... – Andrey Tyukin Mar 21 '18 at 17:09
  • But I have to agree: "Just try to implement it, you will see that you are forced to put `asInstanceOf` on everything" is a good hint overall. One can't understand it until one actually tries to rebuild a little toy collection-library on your own. – Andrey Tyukin Mar 21 '18 at 17:12
  • I don't think that a mutable Tuple can be implemented as covariant, if you know how to do it I would be very interested in see it – Mikel San Vicente Mar 21 '18 at 17:25
  • of course using asInstanceOf is not acceptable for a valid implementation – Mikel San Vicente Mar 21 '18 at 17:27
  • Indentation will probably break, but here you go: `class T[+A](a1: A, a2: A) { private[this] var v1: A = a1 private[this] var v2: A = a2 def _1: A = v1 def _2: A = v2 def swap(): Unit = { val temp = v2 v2 = v1 v1 = temp } } val t1: T[String] = new T("hello", "world") val t2: T[Any] = t1 // is ok, covariant println(t2._1) t1.swap() println(t2._1) // obviously mutable` The only thing that one might object is that "mutable" is a [red herring](https://ncatlab.org/nlab/show/red+herring+principle) and used in the sense of "not immutable". – Andrey Tyukin Mar 21 '18 at 18:50
  • well, you didn't implement any covariant operation... that's where you will have problems, but if it is only about T[A] <: T[Any] then it's true that it can be done, but no functionality like setting _2 can be implemented – Mikel San Vicente Mar 21 '18 at 19:09
  • but I see your point that is technically possible for a very restricted structures – Mikel San Vicente Mar 21 '18 at 19:10
  • "Very restricted" is difficult to quantify. My point is rather: covariance/invariance is more or less orthogonal to any other property like mutability/immutability, growability, or whatever. Neither property strictly implies the other. E.g. immutability and covariance, while being positively correlated in most libraries, do not imply each other, one can always come up with a somewhat contrived, but valid counterexample. – Andrey Tyukin Mar 21 '18 at 19:20
  • I understand, my point was that in Scala it is not possible to write any covariant mutable structure that have covariant operations, so you would be restricted to a few mutate operations like `swap` that don't take an A as paramter, but if you try to implement `set_2` you won't be able. – Mikel San Vicente Mar 21 '18 at 19:27