Kevin Meredith Kevin Meredith - 2 months ago 16
Scala Question

Better Way to Handle Nested Monad?

Given a list of names:

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> val names: Future[List[String]] =
Future.successful( List("Joe Foo", "Jane Bar") )
names: scala.concurrent.Future[List[String]] =
scala.concurrent.impl.Promise$KeptPromise@3dddbe65


And, a method that, for a given
name
, returns a
Future[String]
for that name's hobby:

scala> def getHobby(name: String): Future[String] = Future.successful( "poker" )
getHobby: (name: String)scala.concurrent.Future[String]


With
names
, i.e.
Future[List[String]]
, I can get those names' hobby via:

scala> names.map(_.map(getHobby))
res3: scala.concurrent.Future[List[scala.concurrent.Future[String]]] =
scala.concurrent.impl.Promise$DefaultPromise@42c28305


But, then I have a somewhat hard-to-read, nested monad,
Future[List[Future[String]]
.

I can clean it up:

scala> res3.map(Future.sequence(_))
res5: scala.concurrent.Future[scala.concurrent.Future[List[String]]] =
scala.concurrent.impl.Promise$DefaultPromise@4eaa375c

scala> res5.flatMap(identity)
res6: scala.concurrent.Future[List[String]] =
scala.concurrent.impl.Promise$DefaultPromise@101bdd1c


And then gets it value.

scala> res6.value
res7: Option[scala.util.Try[List[String]]] = Some(Success(List(poker, poker)))


But, is there a cleaner, more idiomatic way to perform the above work?

Answer

One approach is to flatten the data as it's retrieved.

scala> names.flatMap(x => Future.sequence(x.map(getHobby)))
res54: scala.concurrent.Future[List[String]] = scala.concurrent.impl.Promise$DefaultPromise@45cd45aa
Comments