Ram
Ram

Reputation: 937

Graceful alternative to map a Future[Option[...]]

I have 2 methods that return Future[Option[String]] and need to invoke the second method if the first method returns a Future[None]. Following is the code I have so far:

def method1(): Future[Option[String]] = Future(None)
def method2(): Future[Option[String]] = Future(Some("test"))

val resultFuture = method1().flatMap(option =>
if (option.isDefined) { // Alternatives?
  Future(option)
} else {
  method2()
})

val durationSeconds = {
  import scala.concurrent.duration._
  import scala.language.postfixOps
  20 seconds
}
val result = Await.result(resultFuture, durationSeconds)
println(result) // Prints Some(test) as expected

I think this solution is deficient because

  1. The success value (the if portion) is being mapped although there is no actual value transformation
  2. Un-necessary creation of a second Future(in the if block) to keep the compiler happy.

I have tried

  1. method1().flatMap(option => option.fold(method2)(value => Future(Option(value)))). But this has the same issue.
  2. option.orElse(...), but it needs to return an Option[B] not Future[Option[B]].

Appreciate any pointers on mapping the Future[None] without explicitly mapping/passing-through the Future[Some[String]].

Upvotes: 4

Views: 426

Answers (2)

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22605

Future.apply creates a new future and schedules it to run on the execution context, which is indeed costly, so you're right it's not the best solution.

Instead of Future.apply you could just use Future.sucessful, which creates already resolved Future and is a very cheap operation. You could also make your code (arguably) more readable with pattern matching and for-comprehension:

for{
    m1 <- method1()
    result <- m1 match {
      case x @ Some(_) => Future.successful(x) //just lifts value into Future context
      case None => method2()
   }
} yield result

You could also use a flat map with pattern matching:

method1().flatMap{
   case x @ Some(_) => Future.successful(x)
   case None => method2()
}

Another solution you might consider is using monad transformer OptionT which simplifies operations on nested monad stack like Future[Option[T]]. Both scalaz and cats provide its own implementations of OptionT. Below an example with cats:

import cats.data.OptionT
import cats.implicits._

OptionT(method1()).orElseF(method2()).value

Upvotes: 4

jwvh
jwvh

Reputation: 51271

Here's one alternative.

val resultFuture :Future[Option[String]] = method1().flatMap(
  _.fold(method2())(str => Future.successful(Some(str))))

It still re-wraps the populated Option but there's less overhead as a Future.successful().

Upvotes: 2

Related Questions