简体   繁体   中英

Dependent futures

Starting playing with Scala futures, I get stuck with dependent futures.

Let's get a example. I search for places and get a Future[Seq[Place]] . For each of theses places, I search for the closest subway stations (the service resurns a Future[List[Station]] ).

I would write this:

Place.get()
.map { places =>
    places.map { place =>
        Station.closestFrom(place).map { stations =>
            SearchResult(place, stations)
        }
    }
}

That thing will make me get a Future[Seq[Future[SearchResult]]] ... which is... not what I would have expected.

What did I miss to get a Future[Seq[SearchResult]] ?

Thanks for all,

Alban

You are missing two Future concepts in your solution: flatMap and Future.sequence

To explain each:

flatMap is like map except instead of giving it a function from future.map(A => B) you give it a function from future.flatMap(A => Future[B]) . This way you can chain Futures together.

Future.sequence is a helper function that combines a list of futures to a future of a list: Seq[Future[A]] => Future[Seq[A]]

Using these two features of the Future API we can change your answer to be:

Place.get().flatMap { places =>
    Future.sequence(places.map { place =>
        Station.closestFrom(place).map { stations =>
            SearchResult(place, stations)
        }
    })
}

Short version

Working with futures is generaly easier using for-comprehension rather than directly map/flatMap. In your situation it should look like this:

for {places        <- Place.get()
     searchResults <- Future.traverse(places)(place => for (stations <- Station.closestFrom(place))
                                                       yield SearchResult(place,stations)
                                              )
} yield searchResults

Detailed Version

Future being a monad, it offers you several ways to chain your operations.

  • If you want to apply an 'regular' function f : A => B to what's inside the box myfuture : Future[A] , indeed map is the way to get a Future[B] . But in the present situation Station.closestFrom a does not give you a List[Stattion] but a Future[List[Station]] .
  • If you want to apply a monadic operation h : A => Future[B] or chain several of them (here Places.get and Station.closestFrom ), flatMap is the way to go. Apply h to a Future[A] gives you a Future[B] .
  • If you want to apply a monadic operation h : A => Future[B] to a collection like a places : Seq[A] , you should use Future.traverse : Seq[A] => (A => Future[B]) => Future[Seq[B]] .

Furthermore, Scala's for-compresention is just syntactic sugar for flatMap/map so instead of writing complex code using those directly you can use a clean and clear for loop. The loop:

for { variable1 <- f1
      variable2 <- f2
} yield expression

is equivalent to (without optimisations) :

f1.flatMap( variable1 => f2.map(variable2 => expression))

Don't hesitate to use for-comprehension, it really helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM