DISCLAIMER: I'm not an expert on this, so at some level this is an educated guess, but I'm going to take a stab at this.
Let's take a look at what the compiler might (again, I don't actually know how it works internally) be thinking as it examines those lines. We'll start with something that works.
val s = List("ab").toSet
The compiler looks at that and says, OK, I've got a List[String]
there, and the code says to call toSet
on that. toSet
is defined as toSet[B >: A]
, so I can think of that as toSet[B >: String]
. All right, so s
is a Set
of some type B
that is a supertype of String
. OK, I've reached the end of the expression and I should make B
the most specific type I can that encompasses everything. So B
is String
, and s
is a Set[String]
.
Next up, let's look at let's look at what doesn't work.
List("ab").toSet.head.head
The compiler starts out the same way. I've got a List[String]
and I'm calling toSet
. That's defined as toSet[B >: A]
so that means I'm working with a Set
of some supertype of String
. The expression then says to grab the head
of the set. All right, I can do that. I don't know what type I can promise, though. I just know it's a supertype of String
. What's he want next? Oh...he wants to call head
on that...Bummer. I don't know if B
has a head
method.
Now, let's look at something else that works.
(List("ab").toSet + "abc").head.head
Compiler again starts the same way. I've got List[String]
and I'm calling toSet[B >: String]
. OK, so I'm working with a Set
of B
s. So what's next. OK, he wants to call +
on that, and he's calling that with something that I know is a String
. Set
is invariant, so if it's a set that can +
a String
, then my B
I'm working with must be a String
. Nailed it. I've got a Set[String]
. Now he wants the head
of that Set
. Fantastic. Here's your String
. Now he wants the head
of that. Gotcha. Here's your Char
.
Note that the following won't work.
(List("ab").toSet + "abc".asInstanceOf[Any]).head.head
The compiler follows the same path as the previous example, but discovers that you're trying to +
an Any
, so you end up with a Set[Any]
instead, and the final head
call fails.
Slight tangent. I'm not entirely sure why toSet
is defined as toSet[B >: A]
, but I suspect that it has something to do with the fact that Set
s are invariant. toList
, toStream
and toIterator
are all defined with [A]
, and Lists
, Streams
, and Iterators
are all covariant. toBuffer
and toSet
are both defined as [B >: A]
, and Buffer
and Set
are both invariant.
So great. How do we tell it what we really want? Our problems stem from the declaration of toSet[B >: A]
. If it were toSet[A]
, we'd be home free. Fortunately, there's also a method called to[Col_]]: Col[A]
. Which means that it returns a collection of the original type, but of a different collection type. That sounds funny, but basically it means that if you call
List("ab").to[Vector]
you get a Vector[String]
. And, critically, if you call
List("ab").to[Set]
you get a Set[String]
right away. So what you actually want to call in your example is
List("ab").to[Set].head.head