MNY
MNY

Reputation: 1536

clean way to handle future of option in scala func

I have some simple enum name Result of fail/success

sealed trait Result

case object Success extends Result

case object Fail extends Result

and I have a function that get nothing and returns Future of Result,

def do(): Future[Result] = {

    someService.getInfo() flatMap { responseInfo =>
      responseInfo.fold(Future.successful[Result](Result)) { info =>
       myDB.getData(info.resId) map { dbRes =>
            if (dbRes.isSomething) Fail else Success
        } recover {
           case e: NotFoundException => Fail
         }
      }
    }
}

I dont like my solution, it looks to messy, is there a simpler one?

someService.getInfo returns Future[Option[ResponseInfo]]

and what i want to do here is incase someService.getInfo returns None so the func should return Future[Fail] else to call the db, which also returns Future of a case class and base on the response to return Fail/Success.

but im looking for a super stylish scala way to make it look good :)

thanks!

Upvotes: 1

Views: 2767

Answers (1)

flavian
flavian

Reputation: 28511

You're creating a new concept of Success and Failure, which a scala.concurrent.Future already has, so what you are doing is pretty pointless.

You have all the failure state representation you could want, because inside a Future hides a scala.util.Try, so you can already match, fold, etc, any other normal flow control method is there.

def do(): Future[Option[DbResult]] = {

    someService.getInfo() flatMap { responseInfo =>
      responseInfo.fold(Future.successful(_)) { info =>
       myDB.getData(info.resId) map { dbRes =>
            if (dbRes.isSomething) Some(dbRes) else None
        } recover {
           case e: NotFoundException => Future.failed(e)
        }
      }
    }
}

It's also not a good idea to conflate notions. E.g "not finding" a result in the database for a given query is a very valid state, and it has nothing to do with your failed future.

Failures in a future should represent network failures, malformed queries, etc, not a valid roundtrip where the database correctly returned no results. For that reason you can use Option.

Monad transformers

This is a generic problem, of transforming G[F[_]] to F[G[_]], where you have interesting combinations of wrapped types to deal with. Say Future[Option[T]] to Option[Future[T]]. The functional way of dealing with this stuff is a little bit hairy, but worth it.

import cats.data.OptionT
import cats.std.future._

def do() = {
  val chain = for {
    info <- OptionT(someService.getInfo())
    data <- OptionT(myDB.getData(info.resId) map { dbRes =>
            if (dbRes.isSomething) Some(dbRes) else None
        } recover {
           case e: NotFoundException => Future.failed(e)
        })
  } yield data

  chain.value
}

It would look similar to above. Read more about OptionT here, but it looks like what you want.

Upvotes: 2

Related Questions