用于Scala中Streams的用例

在Scala中有一个Stream类,非常像迭代器。 迭代器和stream在斯卡拉之间的主题差异? 对两者之间的相似性和差异提供了一些见解。

看到如何使用stream是非常简单的,但我没有很多常见的使用情况下,我会使用stream而不是其他工件。

我现在的想法是:

  • 如果你需要使用无限的系列。 但是这对我来说似乎不是一个常见的用例,所以它不符合我的标准。 (请纠正我,如果这是常见的,我只是有一个盲点)
  • 如果您有一系列数据需要计算每个元素,但您可能需要重复使用多次。 这很弱,因为我可以将它加载到一个列表中,这个列表在开发人员群体的大部分概念上更容易遵循。
  • 也许有大量的数据或计算成本很高的系列,并且很有可能你所需要的项目不需要访问所有的元素。 但是在这种情况下,迭代器将是一个很好的匹配,除非需要进行多次search,在这种情况下,即使效率稍低,也可以使用列表。
  • 有一系列复杂的数据需要重复使用。 这里也可以使用一个列表。 虽然在这种情况下,这两种情况将同样难以使用,并且由于不是所有的元素都需要被加载,所以Stream将会更好。 但是,不是那么平常……还是这样?

那么我错过了什么大用途? 或者大部分是开发人员偏好?

谢谢

StreamIterator之间的主要区别是后者是可变的和“一次性的”,可以这么说,而前者不是。 IteratorStream有更好的内存占用,但是可变的事实可能是不方便的。

拿这个经典的素数生成器,例如:

 def primeStream(s: Stream[Int]): Stream[Int] = Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 })) val primes = primeStream(Stream.from(2)) 

它也可以很容易地用一个Iterator来编写,但是一个Iterator不会保留到目前为止所计算的素数。

所以, Stream一个重要方面就是可以将它传递给其他函数,而不必先复制它,或者一次又一次地生成它。

至于昂贵的计算/无限列表,这些事情也可以用Iterator完成。 无限列表实际上是相当有用的 – 你只是不知道它,因为你没有它,所以你已经看到了algorithm,而不是仅仅为了处理强制的有限尺寸而必须的。

除了Daniel的回答之外,请记住, Stream对于短路评估很有用。 例如,假设我有一大堆使用String并返回Option[String]的函数,我想继续执行它们直到其中一个工作:

 val stringOps = List( (s:String) => if (s.length>10) Some(s.length.toString) else None , (s:String) => if (s.length==0) Some("empty") else None , (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None ); 

那么,我当然不希望执行整个列表, List上没有任何方便的方法,说:“把它们当做函数并执行它们,直到它们中的一个返回非None ”。 该怎么办? 也许这个:

 def transform(input: String, ops: List[String=>Option[String]]) = { ops.toStream.map( _(input) ).find(_ isDefined).getOrElse(None) } 

这需要一个列表并将其视为一个Stream (实际上并不计算任何东西),然后定义一个新的Stream ,这是应用函数的结果(但目前还不计算任何东西),然后search第一个一个是定义的 – 在这里神奇地,它回头看,并意识到它必须应用地图,并从原始列表中获取正确的数据,然后解包它从Option[Option[String]] Option[String]使用getOrElse

这是一个例子:

 scala> transform("This is a really long string",stringOps) res0: Option[String] = Some(28) scala> transform("",stringOps) res1: Option[String] = Some(empty) scala> transform(" hi ",stringOps) res2: Option[String] = Some(hi) scala> transform("no-match",stringOps) res3: Option[String] = None 

但是它工作? 如果我们把一个println放到我们的函数中,那么我们可以告诉它们是否被调用

 val stringOps = List( (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None }, (s:String) => {println("2"); if (s.length==0) Some("empty") else None }, (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None } ); // (transform is the same) scala> transform("This is a really long string",stringOps) 1 res0: Option[String] = Some(28) scala> transform("no-match",stringOps) 1 2 3 res1: Option[String] = None 

(这是Scala 2.8; 2.7的实现有时会超过一个,不幸的是,请注意,当你的失败产生时,你累积一长串None ,但是相比于你真正的计算,这可能是相当便宜的。

我可以想象,如果你实时轮询一些设备,Stream更方便。

想想一个GPS跟踪器,如果你问的话它会返回实际的位置。 您无法预先计算您将在5分钟内的位置。 你可能只用了几分钟就可以在OpenStreetMap中实现一条path,或者你也可以在沙漠或雨林中用六个月的时间来进行探险。

或者数字温度计或其他types的传感器,只要硬件处于活动状态并打开,可以重复返回新的数据 – 日志文件filter可以是另一个例子。

Stream是作为immutable.List Iterator 。列表是mutable.List 。 有利于不变性防止了一类错误,偶尔以性能为代价。

scalac本身并不能解决这些问题: http ://article.gmane.org/gmane.comp.lang.scala.internals/2831

正如Daniel指出的那样,赞成懒惰超过严格可以简化algorithm并使其更容易组合。