使用返回Future的函数映射Stream

时间:2013-08-04 14:01:26

标签: scala stream future

我有时发现自己处于某种Stream[X]function X => Future Y的情况,我希望将其与Future[Stream[Y]]结合起来,而我似乎无法找到一种方法来做到这一点。例如,我有

val x = (1 until 10).toStream
def toFutureString(value : Integer) = Future(value toString)

val result : Future[Stream[String]] = ???

我试过

 val result = Future.Traverse(x, toFutureString)

它给出了正确的结果,但似乎在返回Future之前消耗了整个流,这或多或少地击败了purpse

我试过

val result = x.flatMap(toFutureString)

但不能使用type mismatch; found : scala.concurrent.Future[String] required: scala.collection.GenTraversableOnce[?]

进行编译
val result = x.map(toFutureString)

返回有点奇怪且无用的Stream[Future[String]]

我应该怎么做才能解决问题?

编辑:我没有停留在Stream上,我对Iterator上的相同操作同样满意,只要它在开始之前不会阻止评估所有项目处理头

Edit2:我不是100%确定Future.Traverse构造在返回Future [Stream]之前需要遍历整个流,但我认为确实如此。如果没有,那本身就是一个很好的答案。

Edit3:我也不需要将结果按顺序排列,我可以使用流或迭代器返回任何顺序。

3 个答案:

答案 0 :(得分:9)

你使用traverse走在正确的轨道上,但不幸的是,在这种情况下,标准库的定义看起来有点破碎 - 它不需要在返回之前使用流。

Future.traverse是一个更通用的函数的特定版本,适用于以“可遍历”类型包装的任何应用程序仿函数(请参阅these papers或我的回答{{3}例如,获取更多信息。

here库提供了这个更通用的版本,在这种情况下它可以正常工作(请注意,我从Scalaz获取了Future的应用程序仿函数实例;它不是但是在Scalaz的稳定版本中,仍然是针对Scala 2.9.2交叉构建的,它没有这个Future):

import scala.concurrent._
import scalaz._, Scalaz._, scalaz.contrib.std._

import ExecutionContext.Implicits.global

def toFutureString(value: Int) = Future(value.toString)

val result: Future[Stream[String]] = Stream.from(0) traverse toFutureString

这会在无限流中立即返回,因此我们确定它首先没有消耗。


作为一个脚注:如果你看Future.traverse foldLeft {{1}},你会看到它是以{{1}}的形式实现的,这很方便,但不是必要或不合适的溪流。

答案 1 :(得分:2)

忘记Stream:

import scala.concurrent.Future
import ExecutionContext.Implicits.global

val x = 1 to 10 toList
def toFutureString(value : Int) = Future {
  println("starting " + value)
  Thread.sleep(1000)
  println("completed " + value)
  value.toString
}

收益率(在我的8核盒子上):

scala> Future.traverse(x)(toFutureString)
starting 1
starting 2
starting 3
starting 4
starting 5
starting 6
starting 7
starting 8
res12: scala.concurrent.Future[List[String]] = scala.concurrent.impl.Promise$DefaultPromise@2d9472e2

scala> completed 1
completed 2
starting 9
starting 10
completed 3
completed 4
completed 5
completed 6
completed 7
completed 8
completed 9
completed 10

因此,其中8个立即被启动(每个核心一个,虽然可以通过线程池执行器进行配置),然后随着那些更多的启动而被启动。 Future [List [String]]立即返回,然后在暂停后开始打印那些“已完成的x”消息。

使用此示例的示例可能是当您有一个List [Url],以及类型为Url =>的函数时。未来[HttpResponseBody。您可以使用该函数在该列表上调用Future.traverse,并并行启动这些http请求,返回一个单独的未来,即结果列表。

是不是像你想要的那样?

答案 2 :(得分:0)

已接受的答案不再有效,因为现代版本的Scalaz traverse()的行为有所不同,并尝试在调用时消耗整个流。

关于这个问题,我想说这是不可能实现真正的非阻塞方式的。

Future[Stream[Y]]Stream[Y]可用之前无法解析。并且由于Y是由函数X => Future[Y]异步生成的,因此您在不阻塞遍历Y的时间的情况下就无法获得Stream[Y]。这意味着要么所有Future[Y]都必须在解析Future[Stream[Y]]之前被解析(这需要消耗整个流),要么必须在遍历Stream[Y]时(在其基础期货的项目上)允许块发生尚未完成)。 但是,如果我们允许阻塞运行,那么最终结果完成的定义是什么?从这个角度来看,它可能与Future.successful(BlockingStream[Y])相同。在语义上又等于原始的Stream[Future[Y]]

换句话说,我认为问题本身就是一个问题。