Wierd difference in datatype for mutable and immutable collections

    val a: mutable.Buffer[_ >: (Map[String, String], Some[String]) with (Map[String, String], None.type) <: (Map[String, String], Option[String])]  = if (true) { 
      mutable.Buffer((Map("" -> ""), Some("")))
    } else {
      mutable.Buffer((Map("" -> ""), None))
    }

Questions:

  1. Can someone explain the datatype of the variable ‘a’ in the code snippet!
  2. Why is there a difference in the datatype of ‘a’ when using immutable collection like Seq or List?

As a prelude, please don’t post screenshots like this. Copy and paste into a ```-delimited block, like I’m about to do. (It would indeed be kind of you to edit your post this way now, so that those who cannot read your screenshot can tell what you’re asking.)

For your second question, you’ve done a fine job breaking it down, but it can go even further towards its essence. The difference is exactly that between Buf and ImSeq in the following test.

trait Imagine {
  type Top
  type IfLeft <: Top
  type IfRight <: Top
  type Buf[A]
  type ImSeq[+A]

  def Buf[A]: Buf[A]
  def ImSeq[A]: ImSeq[A]

  def bufTrial = if (true) Buf[IfLeft] else Buf[IfRight]
  def seqTrial = if (true) ImSeq[IfLeft] else ImSeq[IfRight]
}


scala> def trialTypes(i: Imagine) = (i.bufTrial, i.seqTrial)
trialTypes: (i: Imagine)(i.Buf[_1], i.ImSeq[i.Top])
  forSome { type _1 >: i.IfLeft with i.IfRight <: i.Top }

To digress to your first question: in your example, the type argument to Buffer takes the form E >: LB <: UB. (In my Buf case, too.) In your example, E is elided, whereas it’s the explicitly bound _1 in my example; the difference is of no consequence in this example.

The LB you see (make sure to read all the way to the <:!) is the intersection or greatest lower-bound of the two possible values for the type parameter; this is much more obvious in my example.

The UB you see is the least upper-bound (in a sense, “union”) of the two possible values for the type parameter. In my example, the abstract type Top is the best choice, so that is what is chosen.

Returning to your second question: the declaration of my ImSeq, as well as Seq and List, implies that for all T, ImSeq[_ <: T] = ImSeq[T]. The latter form is simpler, i.e. just showing the UB, and more probably what you want (it is in this case, right?), so Scala inference just picks that.

On the other hand, this is not and cannot be true for Buffer. So you get the full form written out.

Sorry, subtyping is kind of complicated.

In Scalaz, we avoid this sort of thing by providing some and none functions that return the desired Option[T] types rather than subtypes thereof. You can use these simple utility functions, or explicit : Ascription to get the type you desire.

The problem with your expression is that it forces the compiler to infer a type from a convoluted expression with lots of moving parts. The compiler knows that it’s a Buffer[Tuple2], but what are the contents of the tuple? In one case, it’s a (Map[String, String], Some[String]), whereas in the other case it’s a (Map[String, String], None.type). The parent type of both of these is (Map[String, String], Option[String]).

Simplifying the expression helps the compiler infer a simpler type:

scala> val value = if (true) Some("") else None
value: Option[String] = Some()

val a = mutable.Buffer((Map("" -> ""), value))
a: scala.collection.mutable.Buffer[(scala.collection.immutable.Map[String,String], Option[String])] = ArrayBuffer((Map("" -> ""),Some()))