4
votes

I'm pretty new to Scala, please bear with me. I have a bunch of futures wrapped in a large array. The futures have done their hard work looking through a few TBs of data, and at the end of my app I want to wrap up all the results of said futures so I can present them nicely.

The collection of futures I have is of the following type:

Array[Future[List(String, String, String)]]

Everything I've read so far about the for-comprehension show that

val test: Seq[Seq[List[String]]] = Seq(Seq(List("Hello", "World"), List("What's", "Up")))

val results = for {
  test1 <- test
  test2 <- test1
  test3 <- test2
} yield test3

Results in

results: Seq[String] = List(Hello, World, What's, Up)

By that same logic, my intention was to do it like so, since I recently discovered that Option, Try, Failure and Success can be treated as collections:

val futures = { ... } // Logic that collects my Futures

// futures is now Array[Future[List(String, String, String)]]

val results = for {
  // futureSeq as Seq[List(String, String, String]
  futureSeq <- Future.sequence(futures.toSeq)
  // resultSet as List(String, String, String)
  resultSet <- futureSeq
} yield resultset

But this doesn't to work. I seem to be receiving the following compilation errors:

Error:(78, 15) type mismatch;
found : Seq[List(String, String, String)]
required: scala.concurrent.Future[?]

resultSet <- futureSeq ^

The part with the required: scala.concurrent.Future[?] is throwing me off completely. I do not understand at all why a Future would be required there.

I have checked the types of all my objects through the REPL, by debugging, and by using IntelliJ's type inspection. They seem to confirm that I'm not just confused about my types.

And before anyone mentions, yes, I am aware that the for-comprehension is syntactic sugar for a bunch of maps, flatMaps and withFilters.

1
According to scala-lang.org/api/2.11.7/#scala.concurrent.Future$, Future.sequence "Transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]" so Future.sequence(futures.toSeq) , which is of the form Future.sequence(Seq[Future[List(String, String, String)]]) transforms Seq[Future[List(String, String, String)]] to Future[Seq[List(String, String, String)]].user4322779
@Tris: Doesn't that confirm what I'm saying? futureSeq <- Future.sequence(futures.toSeq) should turn Future[Seq[List(String, String, String)]] into Seq[List(String, String, String)].romatthe
I am saying it goes in the opposite direction and results in one Future[Seq[List(String, String, String)]] which is not traversable. I could use a minimal complete example demonstrating the issue, however I suspect what would work is to seqFuture <- futures.toSeq; resultSet <- seqFuture and then yield resultset which should give a Traversable(String, String, String)user4322779
@Tris: Thanks for pointing that out. I'm staring at this thing for hours and I didn't even see I was doing Future.sequence(futures.toSeq) instead of futures.toSeq. It's still not allowing me to do resultSet <- seqFuture though. required: scala.collection.GenTraversableOnce[?]romatthe
That is because traversing the Seq in Seq[Future[List(String, String, String)]] leaves Future[List(String, String, String)] from which to extract and traverse List(String, String, String) which requires completion of the Future that can be done with Future.result(atMost: Duration). Based on all of this could do: futureFromSeq <- futures.toSeq; resultSet <- futureFromSeq.result() and then yield resultSetuser4322779

1 Answers

3
votes

The details of how the for-comprehension desugars to calls to flatMap and map are important here. This code:

for {
  futureSeq <- Future.sequence(futures.toSeq)
  resultSet <- futureSeq
} yield resultset

Becomes something more or less like this:

Future.sequence(futures.toSeq).flatMap(futureSeq => futureSeq)

The flatMap on Future expects a function that returns a Future, but you've given it one that returns an Seq[List[(String, String, String)]].

In general you can't mix types in for-comprehensions (Option in a sequence comprehension is a kind of exception that's supported by an implicit conversion). If you have a <- arrow coming out of a future, all of the rest of your <- arrows need to come out of futures.

You probably want something like this:

val results: Future[Seq[(String, String, String)]] =
  Future.sequence(futures.toSeq).map(_.flatten)

You could then use something like this:

import scala.concurrent.Await
import scala.concurrent.duration._

Await.result(results.map(_.map(doSomethingWithResult)), 2.seconds)

To synchronously present the results (blocking until its done).