【问题标题】:Semantics of Scala Traversable, Iterable, Sequence, Stream and View?Scala Traversable、Iterable、Sequence、Stream 和 View 的语义?
【发布时间】:2012-01-24 01:33:22
【问题描述】:

还有其他问题,例如 Scala: What is the difference between Traversable and Iterable traits in Scala collections?How would I get the sum of squares of two Lists in Scala? 可以部分回答问题。我觉得在一个地方涵盖所有这些的问题是有道理的。

【问题讨论】:

  • 我投票结束 1) 因为这不是一个真正的具体问题,而是很笼统,2) 因为随着 Scala 的发展,信息可能会变得陈旧,3) 因为一般情况是已经由Scala's own documentation 进行了明确定义,这可能是进行如此广泛调查的更好资源(与许多其他语言不同,Scala 拥有相当广泛的文档,有时甚至颇具挑战性)。
  • @ig0774 2) 在我看来,Scala 集合的核心概念在可观察的未来会发生显着变化。
  • @ig0774 与您的三点相比,我赞成而不是投票结束,因为 1)我觉得这个问题通常很有价值,并且足够有形,可以简洁地回答,2)因为它靠近理解当前 Scala 集合库的核心,以及 3) 当前文档虽然很广泛,但非常分散;很难把握全局。
  • @ig0774 感谢您的链接。我不知道这个文档scala-lang.org/docu/files/collections-api/collections.html。好读。但是,我认为对于正在寻找它的初学者来说,这个问题确实有意义。
  • @om-nom(上周)对此的理论讨论:groups.google.com/group/scala-internals/browse_thread/thread/…

标签: scala scala-collections


【解决方案1】:

Traversable 是集合层次结构的顶部。它的主要方法是'foreach',因此它允许为集合的每个元素做一些事情。

Iterable可以创建一个Iterator,基于它可以实现foreach。这定义了元素的某些顺序,尽管每个迭代器的顺序可能会有所不同。

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

Streams 是惰性序列。 IE。流的元素在被访问之前可能不会被计算。这使得处理无限序列(如所有整数的序列)成为可能。

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

【讨论】:

  • 视图不是懒惰的,只是非严格的。懒惰需要缓存,流需要缓存,但视图不需要。
  • @DanielC.Sobral “懒惰需要缓存” - 嗯,什么?
  • @DanBurton - 这样如果您再次要求相同的值,就不会重新计算。否则,它不会那么“懒惰”:)
  • 也许“memoization”会是一个更好的描述,因为这会阻止值被丢弃,而缓存不是这种情况。
  • @DanielC.Sobral,文档说“Stream 类实现了惰性列表……Stream 类采用了记忆”。懒惰可以有记忆,但不是必须的。 Scala 的lazy val 混淆了这些概念,但它可能比lazy memoized val 更好。
【解决方案2】:

我想添加一条关于流与迭代器的评论。流和迭代器都可用于实现长的、非严格的、可能无限的集合,在需要时才计算值。

但是,这样做时会出现“过早执行”的一个棘手问题,可以使用迭代器而不是流来避免,并且在此过程中指出了两者之间的重要语义差异。这也许最清楚地说明如下:

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
  }
}

此代码创建一个从给定值开始并返回连续整数的无限流。它用作更复杂代码的替身,例如,可能会打开 Internet 连接并根据需要从连接中返回值。

结果:

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 连接,并且在创建流之后和使用任何值之前有很长的延迟,这可能会非常有问题 - 您最终会得到一个打开的文件描述符更糟糕的是,您的互联网连接可能会超时,导致整个过程失败。

使用初始空流修复它的简单尝试不起作用:

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)

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

【讨论】:

  • 您可以在流版本中将“val iter”更改为“def iter”。这样就可以了。
猜你喜欢
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 2022-11-19
  • 1970-01-01
  • 2011-02-05
  • 2016-06-18
  • 2010-11-06
  • 1970-01-01
相关资源
最近更新 更多