我有时发现自己处于某种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:我也不需要将结果按顺序排列,我可以使用流或迭代器返回任何顺序。
答案 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]]
。
换句话说,我认为问题本身就是一个问题。