0
votes

How can I "fix" this response, I wanted Future[Option[F4]]

val f4Response: Future[Option[Future[Option[Future[F4]]]]] = 
    for {
        f1Opt <- api.getF1() // Future[Option[F1]]
        f2Opt <- if (f1Opt.isDefined) api.getF2(f1Opt.get.id) else Future.successful(None) // getF2 is Future[Option[F3]]
    } yield {
        for {
            f1 <- f1Opt
            f2 <- f2Opt
        } yield {
            for {
                f3Opt <- api.getF3(f1.id, f2.id) // Future[Option[F3]]
            } yield {
                for {
                    f3 <- f3Opt
                } yield {
                    api.insertF(f1, f2, f3) // Future[Option[F4]]
                }
            }
        }
    }

Update

I'm trying to use scalaz but I am getting an error:

val result: Future[Option[f4]] = (
            for {
              f1 <- OptionT(api.getF1(..))
              f2 <- OptionT(api.getF2(..))
              f3 <- OptionT(api.getF3(f1.id, f2.id)
            } yield api.getF4(f1, f2, f3)
          ).run

Error is:

[error]  found   : scala.concurrent.Future[Option[scala.concurrent.Future[F4]]]
[error]  required: scala.concurrent.Future[Option[F4]]

Also, I can't access f1.id and f2.id in the line:

f3 <- OptionT(api.getF3(f1.id, f2.id)
2

2 Answers

1
votes

That's the perfect fit for cats OptionT monad transformer.

You need some cats imports:

import cats.data.OptionT
import cats.instances.future._

let's say this is your data structure (mocked):

case class F1(id: Int)
case class F2(id: Int)
case class F3(id: Int)
trait F4

object api {
  def getF1(): Future[Option[F1]] = ???
  def getF2(f1: Int): Future[Option[F2]] = ???
  def getF3(f1: Int, f2: Int): Future[Option[F3]] = ???
  def insertF(f1: Int, f2: Int, f3: Int): Future[Option[F4]] = ???
}

then you can do:

val resultT: OptionT[Future, F4] = for {
  f1 <- OptionT(api.getF1())
  f2 <- OptionT(api.getF2(f1.id))
  f3 <- OptionT(api.getF3(f1.id, f2.id))
  f4 <- OptionT(api.insertF(f1.id, f2.id, f3.id))
} yield f4

val result: Future[Option[F4]] = resultT.value

Alternatively you can directly wrap your methods with OptionT :

type FutOpt[T] = OptionT[Future, T]

def getF1(): FutOpt[F1] = OptionT { ??? }
def getF2(f1: Int): FutOpt[F2] = OptionT { ??? }
def getF3(f1: Int, f2: Int): FutOpt[F3] = OptionT { ??? }
def insertF(f1: Int, f2: Int, f3: Int): FutOpt[F4] = OptionT { ??? }

val resultT: FutOpt[F4] = for {
  f1 <- api.getF1()
  f2 <- api.getF2(f1.id)
  f3 <- api.getF3(f1.id, f2.id)
  f4 <- api.insertF(f1.id, f2.id, f3.id)
} yield f4

val result: Future[Option[F4]] = resultT.value

You can also use scalaz OptionT keeping the exact same syntax (except for .value -> .run) just by changing imports.

import scalaz._
import Scalaz._

Having def insertF(f1: Int, f2: Int, f3: Int): Future[F4] instead of Future[Option[F4]] you can rewrite the for-comprehension (using scalaz) as:

val resultT: OptionT[Future, F4] = for {
  f1 <- OptionT(api.getF1())
  f2 <- OptionT(api.getF2(f1.id))
  f3 <- OptionT(api.getF3(f1.id, f2.id))
  f4 <- api.insertF(f1.id, f2.id, f3.id).liftM[OptionT]
} yield f4

val result: Future[Option[F4]] = resultT.run
0
votes
val f4Response: Future[Option[Int]] = api.getF1() flatMap {
  case Some(f1) => {
    api.getF2(f1).flatMap {
      case Some(f2) => {
        api.getF3(f1.id, f2.id).flatMap {
          case Some(f3) => bar(f1, f2, f3)
        }
      }
    }
  }
}

for yield maybe it's unnecessary for this scenario patter match maybe is better, no Option.get directly(it maybe will fail), it's more safe for pattern match.