Scala益智游戏:为什么不使用toSet,而它与Set一起工作?

时间:2013-12-05 15:01:10

标签: scala

有人可以解释为什么会发生以下情况吗?我的意思是如果某些东西的类型为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

2 个答案:

答案 0 :(得分:2)

免责声明:我不是这方面的专家,所以在某种程度上这是一个有根据的猜测,但我会对此进行一次尝试。

让我们看一下编译器可能(再次,我实际上并不知道它是如何在内部工作)的思考,因为它会检查这些行。我们将从有用的东西开始。

val s = List("ab").toSet

编译器会查看它并说,好吧,我在那里有一个List[String],代码说要调用toSettoSet定义为toSet[B >: A],因此我可以将其视为toSet[B >: String]。好的,s是某种类型Set的{​​{1}},是B的超类型。好的,我已经到了表达式的末尾,我应该使String成为我能包含的最具体的类型。因此BBStrings

接下来,让我们来看看的工作原理。

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] SetB。下一个是什么。好的,他想打电话给+,而他用那些我知道的东西叫StringSet是不变的,所以如果它是一个可以+ 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不变的事实有关。 toSettoSet[B >: A]Set都定义为toListtoStreamtoIterator[A]都是协变的。 ListsStreams都定义为IteratorstoBuffertoSet都是不变的。

太好了。我们如何告诉它我们真正想要的是什么?我们的问题源于[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是必要的。