按照下面的示例,调用xs.toList.map(_.toBuffer)
成功,但是xs.toBuffer.map(_.toBuffer)
失败。但是,当使用中间结果执行后者中的步骤时,它会成功。是什么导致这种不一致?
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))
最佳答案
查看toBuffer
和toList
的定义:
def toBuffer[A1 >: A]: Buffer[A1]
def toList: List[A]
如您所见,
toBuffer
是通用的,而toList
不是通用的。这种差异的原因是-我相信-
Buffer
是不变的,而List
是协变的。假设我们有以下类别:
class Foo
class Bar extends Foo
由于
List
是协变的,因此可以在toList
的实例上调用Iterable[Bar]
并将结果视为List[Foo]
。如果
List
不变,则不是这种情况。Buffer
是不变的,如果将toBuffer
定义为def toBuffer: Buffer[A]
,您将同样无法处理结果(在
toBuffer
的实例上)作为Iterable[Bar]
的实例(因为Buffer[Foo]
不是Buffer[Bar]
的子类型,与列表不同)。但是通过将
Buffer[Foo]
声明为toBuffer
(注意添加的类型参数def toBuffer[A1 >: A]
),我们可以得到使A1
返回toBuffer
实例的可能性:我们需要做的就是将
Buffer[Foo]
明确设置为Foo,或者让编译器进行推断(如果在需要A1
的站点上调用toBuffer
)。我认为这解释了
Buffer[Foo]
和toList
定义不同的原因。现在的问题是
toBuffer
是通用的,这会严重影响推理。执行此操作时:
"ab-cd".split("-").toBuffer
您从不会明确地说
toBuffer
是A1
,但是因为String
的类型明确地是"ab-cd".split("-")
,所以编译器知道Array[String]
是A
。它还知道
String
(在A1 >: A
中),并且没有任何进一步的约束,它将推断toBuffer
恰好是A1
,换句话说就是A
。因此,最后整个表达式返回一个
String
。但这就是问题:在scala中,类型推断发生在整个表达式中。
当您有类似
Buffer[String]
的内容时,您可能希望scala可以推断出确切的类型对于
a.b.c
,然后从中推断出a
的确切类型,最后是a.b
的确切类型。不是这样类型推断被推迟到整个表达式
a.b.c
(请参见scala规范“ 6.26.4本地类型推断””,“案例1:选择”)
因此,回到您的问题,在表达式
a.b.c
中,子表达式"ab-cd".split("-").toBuffer.map(_.toBuffer)
的类型不是"ab-cd".split("-").toBuffer
,而是它仍然像
Buffer[String]
这样输入。换句话说,Buffer[A1] forSome A1 >: String
不是固定的,我们只是将约束A1
带到下一步的推理。下一步是
A1 >: String
,其中map(_.toBuffer)
被定义为map
。这里的map[C](f: (B) ⇒ C): Buffer[B]
实际上与B
相同,但是此时A1
尚不完全清楚,我们只知道
A1
。这就是我们的问题。编译器需要知道匿名函数
A1 >: String
的确切类型(这是因为实例化(_.toBuffer)
要求知道Function1[A,R]
和A
的确切类型,就像任何泛型类型一样)。因此,您需要以某种方式明确地告诉他,因为它无法准确推断出它。
这意味着您需要执行以下任一操作:
"ab-cd".split("-").toBuffer[String].map(_.toBuffer)
要么:
"ab-cd".split("-").toBuffer.map((_:String).toBuffer)