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
toBuffer
andtoList
:As you can see,
toBuffer
is generic, whiletoList
is not. The reason for this difference is - I believe - thatBuffer
is invariant, whileList
is covariant.Let's say that we have the following classes:
Because
List
is covariant, you can calltoList
on an instance ofIterable[Bar]
and treat the result as aList[Foo]
. IfList
where invariant, this would not be the case.Buffer
being invariant, iftoBuffer
was 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 declaringtoBuffer
asdef toBuffer[A1 >: A]
(notice the added type parameterA1
), we get back the possibility to havetoBuffer
return an instance ofBuffer[Foo]
: all we need is to explcitly setA1
to Foo, or let the compiler infer it (iftoBuffer
is called at a site where aBuffer[Foo]
is expected).I think this explains the reason why
toList
andtoBuffer
are defined differently. Now the problem with this is thattoBuffer
is generic, and this can badly affect inference.When you do this:
You never explicitly say that
A1
isString
, but because"ab-cd".split("-")
has unambiguously the typeArray[String]
, the compiler knows thatA
isString
. It also knows thatA1 >: A
(intoBuffer
), and without any further constraint, it will inferA1
to 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("-").toBuffer
is not typedBuffer[String]
, but instead it stays typed as something likeBuffer[A1] forSome A1 >: String
. In other words,A1
is not fixed, we just carry the constraintA1 >: String
to the next step of inference. This next step ismap(_.toBuffer)
, wheremap
is defined asmap[C](f: (B) ⇒ C): Buffer[B]
. HereB
is actually the same asA1
, but at this pointA1
is 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 ofA
andR
, 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: