Reputation: 41939
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?
Upvotes: 5
Views: 521
Reputation: 1328972
Just for illustration (of nested monad), you also have "Scalaz Monad Transformers", described by Rama Nallamilli.
Take
Future
for example, all these cases below are monads nested within a monad.
Future[Option[T]]
Future[List[T]]
Future[scalaz.\/[A, B]]
In the case of a nested monad such as Future[List[Int]]
, Rama suggests:
When choosing which monad transformer to use, you always choose the inner most type, in this case List[Int] is our inner most type so we will use the (Scalaz)
ListT
monad transformer.The ListT apply function is as follows:
def apply[A](a: M[List[A]]) = new ListT[M, A](a)
Therefore we can use this to convert our
Future[List[Int]]
to theListT
monad type which in this case will be aListT[Future, Int]
.
We can now write our addition in terms of the new monad type which has abstracted the mapping of theFuture
:
@ for {
i <- ListT(x)
j <- ListT(y)
} yield i + j
res20: ListT[Future, Int] = ListT(Success(List(5, 6, 7, 6, 7, 8, 7, 8, 9)))
In summary:
monad transformers give you a powerful abstraction to work on the underlying data of a monadic type when it itself is wrapped in a monad.
It reduces code complexity and enhances readability by abstracting the wiring of drilling down into the nested datatypes.ScalaZ provides implementations of monad transformers for many types including EitherT,
ListT
,OptionT
andReaderT
to name a few.
Upvotes: 2
Reputation: 51271
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
Upvotes: 5