Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use the result of one service call (List) to get another List

Tags:

scala

I have a service that returns a datatype (Foo below) that contains a list of ids to be used for a second service call getBar below

case class Foo(barIds: List[Int])
case class Bar(id: Int)

val f = Future(List(Foo(List(1, 2)), Foo(List(5, 6))))
def getBar(l: List[Int]) = Future(l.map(Bar(_)))

What i need is Future[List[Foo,List[Bar]]]

I tried first a nested for-comprehension but

val rr = for {
    foos <- f
} yield  for {
    foo <- foos
    bars <- getBar(foo.barIds) // won't work as this is a Future and foos is a list
} yield (foo,bars)   

I then played a mapping game, (which smells horrible):

f.map(
    foos => foos.map(foo => (foo, foo.barIds)))
        .map(ts => ts.map(t => (t._1, getBar(t._2)))
)

But that gives me a Future[List[Foo,Future[List[Bar]]]]

There should be way to get Future[List[Foo,List[Bar]]] and hopefully in a much cleaner way

Here is a scalafiddle https://scalafiddle.io/sf/P0FRIGs/0

Note the value i am after is: tuples with Foo and a list of "their" associated Bar values:

List(
     (Foo(List(1, 2)),List(Bar(1), Bar(2))),
     (Foo(List(5, 6)),List(Bar(5), Bar(6)))
)
like image 255
jen Avatar asked Jun 13 '18 10:06

jen


2 Answers

I'd create a helper wrapper method around getBar that returns the foo passed in, and combine that with Future.traverse like so:

private def getFooAndBars(foo: Foo): Future[(Foo, List[Bar])] =
  getBar(foo.barIds).map(foo -> _)

val res: Future[List[(Foo, List[Bar])]] =
  f.flatMap(Future.traverse(_)(getFooAndBars))

Future.traverse will take each foo, call getFooAndBars on it, and flatten the list so that you get a Future[List] instead of a List[Future].

like image 155
Zoltán Avatar answered Sep 22 '22 09:09

Zoltán


Mapping things into required structured can be tricky sometimes, in such cases you can choose your identifiers in a way that these help you navigate the complexity in a more meaningful way. Here you go.

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

case class Foo(barIds: List[Int])
case class Bar(id: Int)

def getBar(l: List[Int]) = Future(l.map(Bar(_)))

val fooListFuture = Future(List(Foo(List(1, 2)), Foo(List(5, 6))))

// You want to get
//Future[List[(Foo,List[Bar])]]

val yourRequireFuture = fooListFuture.flatMap(fooList => {
  Future.sequence(fooList.map(foo =>
    getBar(foo.barIds).map(barList => (foo, barList))
  ))
})
like image 38
sarveshseri Avatar answered Sep 20 '22 09:09

sarveshseri