Kevin Meredith
Kevin Meredith

Reputation: 41939

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?

Upvotes: 5

Views: 521

Answers (2)

VonC
VonC

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 the ListT monad type which in this case will be a ListT[Future, Int].
We can now write our addition in terms of the new monad type which has abstracted the mapping of the Future:

@ 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 and ReaderT to name a few.

Upvotes: 2

jwvh
jwvh

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

Related Questions