As per the example below, calling xs.toList.map(_.toBuffer) succeeds, but xs.toBuffer.map(_.toBuffer) fails. But when the steps in the latter are performed using an intermediate result, it succeeds. What causes this inconsistency?
scala> "ab-cd".split("-").toBuffer
res0: scala.collection.mutable.Buffer[String] = ArrayBuffer(ab, cd)
scala> res0.map(_.toBuffer)
res1: scala.collection.mutable.Buffer[scala.collection.mutable.Buffer[Char]] = ArrayBuffer(ArrayBuffer(a, b), ArrayBuffer(c, d))
scala> "ab-cd".split("-").toBuffer.map(_.toBuffer)
<console>:8: error: missing parameter type for expanded function ((x$1) => x$1.toBuffer)
"ab-cd".split("-").toBuffer.map(_.toBuffer)
^
scala> "ab-cd".split("-").toList.map(_.toBuffer)
res3: List[scala.collection.mutable.Buffer[Char]] = List(ArrayBuffer(a, b), ArrayBuffer(c, d))
Look at the definitions of
toBufferandtoList:As you can see,
toBufferis generic, whiletoListis not. The reason for this difference is - I believe - thatBufferis invariant, whileListis covariant.Let's say that we have the following classes:
Because
Listis covariant, you can calltoListon an instance ofIterable[Bar]and treat the result as aList[Foo]. IfListwhere invariant, this would not be the case.Bufferbeing invariant, iftoBufferwas defined asdef toBuffer: Buffer[A]you would similarly not be able to treat the result oftoBuffer(on an instance ofIterable[Bar]) as an instance ofBuffer[Foo](asBuffer[Bar]is not a sub-type ofBuffer[Foo], unlike for lists). But by declaringtoBufferasdef toBuffer[A1 >: A](notice the added type parameterA1), we get back the possibility to havetoBufferreturn an instance ofBuffer[Foo]: all we need is to explcitly setA1to Foo, or let the compiler infer it (iftoBufferis called at a site where aBuffer[Foo]is expected).I think this explains the reason why
toListandtoBufferare defined differently. Now the problem with this is thattoBufferis generic, and this can badly affect inference.When you do this:
You never explicitly say that
A1isString, but because"ab-cd".split("-")has unambiguously the typeArray[String], the compiler knows thatAisString. It also knows thatA1 >: A(intoBuffer), and without any further constraint, it will inferA1to be exactlyA, in other wordsString. So in the end the whole expression returns aBuffer[String].But here's the thing: in scala, type inference happens in an expression as a whole. When you have something like
a.b.c, you might expect that scala will infer an exact type fora, then from that infer an exact type fora.b, and finally fora.b.c. Not so. Type inference is deferred to the whole expressiona.b.c(see scala specification "6.26.4 Local Type Inference ", "case 1: selections")So, going back to your problem, in the expression
"ab-cd".split("-").toBuffer.map(_.toBuffer), the sub-expression"ab-cd".split("-").toBufferis not typedBuffer[String], but instead it stays typed as something likeBuffer[A1] forSome A1 >: String. In other words,A1is not fixed, we just carry the constraintA1 >: Stringto the next step of inference. This next step ismap(_.toBuffer), wheremapis defined asmap[C](f: (B) ⇒ C): Buffer[B]. HereBis actually the same asA1, but at this pointA1is still not fully known, we only know thatA1 >: String. Here lies our problem. The compiler needs to know the exact type of the anonymous function(_.toBuffer)(simply because instantiating aFunction1[A,R]requires to know the exact types ofAandR, just like for any generic type). So you need to tell him explcitly somehow, as it was not able to infer it exactly.This means you need to do either:
Or: