mapping a Stream with a function returning a Future

后端 未结 3 1885
礼貌的吻别
礼貌的吻别 2021-02-13 11:29

I sometimes find myself in a situation where I have some Stream[X], and a function X => Future Y, that I\'d like to combine to a Future[Stream

相关标签:
3条回答
  • 2021-02-13 11:50

    The accepted answer is no longer valid as the modern version of Scalaz traverse() behaves differently and tries to consume the entire stream on the invocation time.

    As to the question I would say that it's impossible to achieve this a truly non-blocking fashion.

    Future[Stream[Y]] cannot be resolved until Stream[Y] is available. And since Y is produced asynchronously by the function X => Future[Y] you cannot get Y without blocking on the time when you traverse Stream[Y]. That means that either all the Future[Y] must be resolved before resolving Future[Stream[Y]] (which requires consuming the entire stream), or you must allow blocks to occur while traversing Stream[Y] (on items whose underlying futures aren't completed yet). But if we allow for blocking on the traversing then what would be the definition of the completion of the resulting future? From that perspective it could be the same as Future.successful(BlockingStream[Y]). That's in turn semantically equal to the original Stream[Future[Y]].

    In other words, I think there is an issue in the question itself.

    0 讨论(0)
  • 2021-02-13 11:54

    You're on the right track with traverse, but unfortunately it looks like the standard library's definition is a little broken in this case—it shouldn't need to consume the stream before returning.

    Future.traverse is a specific version of a much more general function that works on any applicative functor wrapped in a "traversable" type (see these papers or my answer here for more information, for example).

    The Scalaz library provides this more general version, and it works as expected in this case (note that I'm getting the applicative functor instance for Future from scalaz-contrib; it's not yet in the stable versions of Scalaz, which are still cross-built against Scala 2.9.2, which doesn't have this 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
    

    This returns immediately on an infinite stream, so we know for sure that it's not being consuming first.


    As a footnote: If you look at the source for Future.traverse you'll see that it's implemented in terms of foldLeft, which is convenient, but not necessary or appropriate in the case of streams.

    0 讨论(0)
  • 2021-02-13 11:59

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

    yields (on my 8 core box):

    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
    

    So 8 of them get kicked off immediately (one for each core, though that's configurable via the threadpool executor), and then as those complete more are kicked off. The Future[List[String]] returns immediately, and then after a pause it starts printing those "completed x" messages.

    An example use of this could be when you have a List[Url's], and a function of type Url => Future[HttpResponseBody]. You could call Future.traverse on that list with that function, and kick off those http requests in parallel, getting back a single future that's a List of the results.

    Was something that like what you were going for?

    0 讨论(0)
提交回复
热议问题