Scala可遍历(Traversable)、可迭代(Iterable)、序列(Sequence)、流(Stream)和视图(View)的语义?

34

3
我投票关闭此问题,原因如下:1)这不是一个具体的问题,而是过于泛泛而谈。2)由于Scala语言将会不断发展,所以这些信息很可能变得过时。3)Scala已经有详细定义这个通用情况的官方文档,对于这种广泛的调查,它可能是更好的资源(而Scala的文档相对其他语言来说非常全面,尽管有时也很具有挑战性)。 - ig0774
2
@ig0774 2) 在我看来,Scala集合的核心概念在可预见的未来不会发生重大变化。 - om-nom-nom
7
与你提出的三个观点相反,我点赞而不是投票关闭,原因如下:1)我认为这个问题通常很有价值,足够具体以便简洁回答;2)因为它与理解当前Scala集合库的核心密切相关;3)尽管当前的文档非常广泛,但非常分散,很难得到全局视图。 - Dan Burton
1
@ig0774 谢谢你提供的链接。我之前不知道这个文档http://www.scala-lang.org/docu/files/collections-api/collections.html。很好的阅读材料。但是,我认为对于初学者来说,在这里提出这个问题是有意义的。 - smartnut007
@om-nom(上周)关于这个问题的理论讨论:http://groups.google.com/group/scala-internals/browse_thread/thread/e71eec4168f2ac33# - Gene T
2个回答

34

Traversable是集合层次结构的最顶端。它的主要方法是'foreach',因此允许对集合中的每个元素执行某些操作。

Iterable可以创建一个迭代器,基于该迭代器可以实现 foreach。这定义了元素的某种顺序,尽管对于每个迭代器,该顺序可能会发生变化。

Seq(uence)是具有固定元素顺序的 Iterable。因此,谈论元素的索引是有意义的。

Streams是惰性的 Sequence。即在访问之前可能不会计算流的元素。这使得可以处理无限序列,例如所有整数的序列。

Views是集合的非严格版本。在视图上执行筛选和映射等方法时,仅当相应元素被访问时才执行传递的函数。因此,在巨大的集合上进行映射会立即返回,因为它只是在原始集合周围创建一个包装器。只有在访问元素时,映射才会实际执行(对于该元素)。请注意,视图不是一个类,但是有很多针对各种集合的 XxxView 类。


9
视图并不是懒加载,只是非严格求值。懒加载需要缓存,流的确实现了缓存,但视图没有。 - Daniel C. Sobral
4
“懒惰需要缓存” - 嗯,什么意思? - Dan Burton
3
@DanBurton - 这样,如果您再次请求同一值,它将不会被重新计算。否则,它就不是那么“懒惰” :) - Rogach
9
也许“记忆化”会更好些,因为这个词不包括删除值的情况,而这并非缓存的特点。 - Daniel C. Sobral
1
@DanielC.Sobral,文档中说:“类Stream实现了惰性列表... Stream类使用了记忆化”。惰性可以有记忆化,但不一定需要。Scala的lazy val混淆了这些概念,但它可能比lazy memoized val更好。 - Paul Draper
1
@PaulDraper 关于我所知道的术语,非严格性不需要记忆化。如果你同时拥有非严格性和记忆化,那么你就拥有了惰性。 - Daniel C. Sobral

3

我想补充一下有关流和迭代器的评论。两者都可用于实现长、非严格、潜在无限的集合,只有在需要时才计算一个值。

然而,这样做会出现“过早执行”的棘手问题,使用迭代器可以避免此问题,但使用流则不能,同时也突显了两者之间重要的语义差异。这可以用以下方式清晰地说明:

def runiter(start: Int) {
  // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1)
    loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

这段代码创建了一个从给定值开始的无限流,返回连续的整数。它用作更复杂的代码的替代品,例如打开互联网连接并根据需要返回连接值。

结果:

scala> runiter(3)
(I computed a value,3)
about to loop
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

请注意,在实际使用流的值之前,计算第一个值所需的执行发生在其位置之前。如果此初始执行涉及打开文件或Internet连接,并且在创建流之后使用任何值之前存在长时间延迟,则可能会出现问题 - 您将得到一个未关闭的文件描述符,更糟糕的是,您的Internet连接可能会超时,导致整个过程失败。
使用初始空流的简单尝试无法解决这个问题:
def runiter(start: Int) {
  // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1)
    Stream[Int]() ++ loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

结果(与以前相同):

scala> runiter(3)
(I computed a value,3)
about to loop
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

然而,您可以通过将流更改为具有初始空迭代器的迭代器来解决此问题,尽管这并不明显:

def runiter(start: Int) {
  // Create an iterator that returns successive integers on demand, e.g. 3, 4, 5, ....
  val iter = {
    def loop(v: Int): Iterator[Int] = { println("I computed a value", v); Iterator(v)} ++ loop(v+1)
    Iterator[Int]() ++ loop(start)
  }
  // Now, sometime later, we retrieve the values ....
  println("about to loop")
  for (x <- iter) {
    if (x < 10) println("saw value", x) else return
  }
}

结果:

scala> runiter(3)
about to loop
(I computed a value,3)
(saw value,3)
(I computed a value,4)
(saw value,4)
(I computed a value,5)
(saw value,5)
(I computed a value,6)
(saw value,6)
(I computed a value,7)
(saw value,7)
(I computed a value,8)
(saw value,8)
(I computed a value,9)
(saw value,9)
(I computed a value,10)

请注意,如果您不添加初始的空迭代器,则会遇到与流相同的过早执行问题。

3
在流版本中,你可以将 "val iter" 更改为 "def iter",这样就行了。 - Shiva Wu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接