Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flattening future of option after mapping with a function that return future of option

Tags:

scala

I have a collection of type Future[Option[String]] and I map it to a function that returns Future[Option[Profile]], but this create a return type of Future[Option[Future[Option[Profile]]]] because queryProfile return type is `Future[Option[Profile]]'

val users: Future[Option[User]] = someQuery
val email: Future[Option[String]] = users map(opSL => opSL map(_.email) )
val userProfile = email map {opE => opE map {E => queryProfile(E)}}

I need to use the Profile object contained deep inside val userProfile without unpacking all these levels, what would be the right way to use flatMap or `flatten', or is there a better approach all together ?

like image 852
InsaneBot Avatar asked Jan 07 '23 11:01

InsaneBot


2 Answers

You can get a "partial Future" with something like this:

  val maybeProfile: Future[Profile] = users
    .collect { case Some(u) => u.email }
    .flatMap { email => queryProfile(email) }
    .collect { case Some(p) => p }

Now maybeProfile contains the (completely "naked"/unwrapped) Profile instance, but only if it was able to find it. You can .map it as usual to do something else with it, that'll work in the usual ways.

If you want to ever block and wait for completion, you will have to handle the missing case at some point. For example:

   val optionalProfile: Option[Profile] = Await.result(
     maybeProfile
       .map { p => Some(p) } // Or just skip the last `collect` above
       .recover { case _:NoSuchElementException => None },
     1 seconds
   )

If you are happy with just having Future[Option[Profile]], and would prefer to have the "unwrapping" magic, and handling the missing case localized in one place, you can put the two fragments from above together like this:

val maybeProfile: Future[Option[Profile]] = users 
  .collect { case Some(u) => u.email }
  .flatMap { email => queryProfile(email) }
  .recover { case _:NoSuchElementException => None }

Or use Option.fold like the other answer suggested:

val maybeProfile: Future[Option[Profile]] = users
    .map { _.map(_.email) }
    .flatMap { _.fold[Future[Option[Profile]]](Future.successful(None))(queryProfile) }

Personally, I find the last option less readable though.

like image 137
Dima Avatar answered Jan 17 '23 17:01

Dima


Personally I think a monad transformer such as OptionT provided by scalaz/cats would be the cleanest approach:

val users = OptionT[Future,User](someQuery)
def queryProfile(email:String) : OptionT[Future,Profile] = ...
for {
  u <- users
  p <- queryProfile(u.email)
} yield p
like image 22
melps Avatar answered Jan 17 '23 17:01

melps