有人可以解释为什么会发生以下情况吗?我的意思是如果某些东西的类型为String
,那么我希望在其上运行head
。但是Set("ab").head
有效,而List("ab").toSet.head.head
没有。为什么呢?
$ scala
Welcome to Scala version 2.10.3 (Java HotSpot(TM) Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.
scala> List("ab").toSet
res0: scala.collection.immutable.Set[String] = Set(ab)
scala> Set("ab")
res1: scala.collection.immutable.Set[String] = Set(ab)
scala> List("ab").toSet.head
res2: String = ab
scala> Set("ab").head
res3: String = ab
scala> List("ab").toSet.head.head
<console>:8: error: value head is not a member of type parameter B
List("ab").toSet.head.head
^
scala> Set("ab").head.head
res5: Char = a
答案 0 :(得分:2)
免责声明:我不是这方面的专家,所以在某种程度上这是一个有根据的猜测,但我会对此进行一次尝试。
让我们看一下编译器可能(再次,我实际上并不知道它是如何在内部工作)的思考,因为它会检查这些行。我们将从有用的东西开始。
val s = List("ab").toSet
编译器会查看它并说,好吧,我在那里有一个List[String]
,代码说要调用toSet
。 toSet
定义为toSet[B >: A]
,因此我可以将其视为toSet[B >: String]
。好的,s
是某种类型Set
的{{1}},是B
的超类型。好的,我已经到了表达式的末尾,我应该使String
成为我能包含的最具体的类型。因此B
为B
,String
为s
。
接下来,让我们来看看不的工作原理。
Set[String]
编译器以相同的方式启动。我有List("ab").toSet.head.head
而我正在呼叫List[String]
。这被定义为toSet
,这意味着我正在使用toSet[B >: A]
的某些超类型Set
。然后表达式表示抓住集合的String
。好吧,我能做到。不过,我不知道我能承诺什么类型。我只知道它是head
的超类型。接下来他想要什么?哦......他想打电话给String
......真糟糕。我不知道head
是否有B
方法。
现在,让我们看看其他有用的东西。
head
编译器再次以相同的方式启动。我有(List("ab").toSet + "abc").head.head
而我正在呼叫List[String]
。好的,我正在使用toSet[B >: String]
Set
个B
。下一个是什么。好的,他想打电话给+
,而他用那些我知道的东西叫String
。 Set
是不变的,所以如果它是一个可以+
String
的集合,那么我的<{1>}我正在使用必须成为{ {1}}。搞定了。我有一个B
。现在他想要String
的{{1}}。太棒了。这是你的Set[String]
。现在他想要head
。疑难杂症。这是你的Set
。
请注意,以下操作无效。
String
编译器遵循与前一个示例相同的路径,但发现您正在尝试head
Char
,因此您最终会使用(List("ab").toSet + "abc".asInstanceOf[Any]).head.head
,而最终+
来电失败。
轻微切线。我不完全确定为什么Any
被定义为Set[Any]
,但我怀疑它与head
s不变的事实有关。 toSet
,toSet[B >: A]
和Set
都定义为toList
,toStream
,toIterator
和[A]
都是协变的。 Lists
和Streams
都定义为Iterators
,toBuffer
和toSet
都是不变的。
太好了。我们如何告诉它我们真正想要的是什么?我们的问题源于[B >: A]
的宣言。如果是Buffer
,我们将免费回家。幸运的是,还有 一个名为Set
的方法。这意味着它返回原始类型的集合,但返回不同的集合类型。这听起来很有趣,但基本上它意味着如果你打电话
toSet[B >: A]
你得到toSet[A]
。并且,批判,如果你打电话
to[Col_]]: Col[A]
你马上得到一个List("ab").to[Vector]
。所以你在你的例子中实际想要的是
Vector[String]
答案 1 :(得分:0)
这不是一个防弹的答案,但对于评论来说太长了,所以我会捅它。
起初,我认为这可能与将String隐式转换为StringLike(或任何提供head方法)有关,但是对于length,getBytes等行为是相同的。
除了已经提到的解决方法之外,还有:
scala> List("abc").toSet.head.toString.head
res15: Char = a
在这种情况下,编译器知道B确实有一个toString方法(一切都有),并且该方法必须返回一个String,它可以用来调用head。因此,存储一个中间结果本身并不是在Ian评论中使调用工作的原因。
然后我查看了List scaladoc并看到使用此签名声明了toSet:
def toSet[B >: A]: Set[B]
因此,当你在A列表上调用Set时,你得到一个B的集合,其中B是A的超类型。所以B不一定与A相同,因此不是必须拥有所有相同的方法。似乎编译器知道List(“ab”)。toSet.head必须返回一个B,但是当它尝试编译下一次调用head时,它还不知道B将是一个String。
除此之外,令我困惑的两件事仍然是:(1)为什么编译器只要知道A是String就能将B解析为String? (2)为什么不设置只返回一个[A]?它在TraversableOnce的集合API中深度定义为:
def toSet[B >: A]: immutable.Set[B] = to[immutable.Set].asInstanceOf[immutable.Set[B]]
并且“to”方法定义为:
def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uV]]): Col[A @uV] = {
val b = cbf()
b ++= seq
b.result
}
这是我开始喘气的地方。它使用从TraversableOnce [A]到Set [B]的隐式转换,其中B是A的超类型。我仍然不明白为什么B是必要的。