Convert lock code to use scala flags

My old code looks something like below, where everything blocks call blocking.

I need help converting this to futures use.

def getUserPoints(username: String): Option[Long] db.getUserPoints(username) match { case Some(userPoints) => Some(userPoints.total) case None => { if (db.getSomething("abc").isEmpty) { db.somethingElse("asdf") match { case Some(pointId) => { db.setPoints(pointId, username) db.findPointsForUser(username) } case _ => None } } else { db.findPointsForUser(username) } } } } 

My new API is below where I return futures.

 db.getUserPoints(username: String): Future[Option[UserPoints]] db.getSomething(s: String): Future[Option[Long]] db.setPoints(pointId, username): Future[Unit] db.findPointsForUser(username): Future[Option[Long]] 

How can I proceed to transform the above to use my new API that uses futures.

I tried using for-compr but started getting weirder errors like Future [Nothing].

 var userPointsFut: Future[Long] = for { userPointsOpt <- db.getUserPoints(username) userPoints <- userPointsOpt } yield userPoints.total 

But it gets a little more complicated with all the branch and if clauses and tries to convert them into futures.

+1
asynchronous scala
source share
1 answer

I would say that the first problem with this design is that the Future blocking call port should not wrap an Option type:

Blocking call: def giveMeSomethingBlocking(for:Id): Option[T] Should be: def giveMeSomethingBlocking(for:Id): Future[T] And not: def giveMeSomethingBlocking(for:Id): Future[Option[T]]

A blocking call gives either Some(value) or None , a non-blocking version of Future gives either Success(value) or Failure(exception) , which fully retains the semantics of Option non-blocking way.

With this in mind, we can simulate the process in question using combinators on Future . Let's see how:

First, let's refactor the API with something we can work with:

 type UserPoints = Long object db { def getUserPoints(username: String): Future[UserPoints] = ??? def getSomething(s: String): Future[UserPoints] = ??? def setPoints(pointId:UserPoints, username: String): Future[Unit] = ??? def findPointsForUser(username: String): Future[UserPoints] = ??? } class PointsNotFound extends Exception("bonk") class StuffNotFound extends Exception("sthing not found") 

Then the process will look like this:

 def getUserPoints(username:String): Future[UserPoints] = { db.getUserPoints(username) .map(userPoints => userPoints /*.total*/) .recoverWith{ case ex:PointsNotFound => (for { sthingElse <- db.getSomething("abc") _ <- db.setPoints(sthingElse, username) points <- db.findPointsForUser(username) } yield (points)) .recoverWith{ case ex: StuffNotFound => db.findPointsForUser(username) } } } 

Which type of validation is correct.

Edit


Given that the API is installed on stone, the way to deal with nested monadic types is to define MonadTransformer. In simple words, make Future[Option[T]] new monad, call it FutureO , which can be composed with others of its kind. [one]

 case class FutureO[+A](future: Future[Option[A]]) { def flatMap[B](f: A => FutureO[B])(implicit ec: ExecutionContext): FutureO[B] = { val newFuture = future.flatMap{ case Some(a) => f(a).future case None => Future.successful(None) } FutureO(newFuture) } def map[B](f: A => B)(implicit ec: ExecutionContext): FutureO[B] = { FutureO(future.map(option => option map f)) } def recoverWith[U >: A](pf: PartialFunction[Throwable, FutureO[U]])(implicit executor: ExecutionContext): FutureO[U] = { val futOtoFut: FutureO[U] => Future[Option[U]] = _.future FutureO(future.recoverWith(pf andThen futOtoFut)) } def orElse[U >: A](other: => FutureO[U])(implicit executor: ExecutionContext): FutureO[U] = { FutureO(future.flatMap{ case None => other.future case _ => this.future }) } } 

And now we can rewrite our process, maintaining the same structure as the future composition.

 type UserPoints = Long object db { def getUserPoints(username: String): Future[Option[UserPoints]] = ??? def getSomething(s: String): Future[Option[Long]] = ??? def setPoints(pointId: UserPoints, username:String): Future[Unit] = ??? def findPointsForUser(username: String): Future[Option[Long]] = ??? } class PointsNotFound extends Exception("bonk") class StuffNotFound extends Exception("sthing not found") def getUserPoints2(username:String): Future[Option[UserPoints]] = { val futureOpt = FutureO(db.getUserPoints(username)) .map(userPoints => userPoints /*.total*/) .orElse{ (for { sthingElse <- FutureO(db.getSomething("abc")) _ <- FutureO(db.setPoints(sthingElse, username).map(_ => Some(()))) points <- FutureO(db.findPointsForUser(username)) } yield (points)) .orElse{ FutureO(db.findPointsForUser(username)) } } futureOpt.future } 

[1] with confirmation http://loicdescotte.imtqy.com/posts/scala-compose-option-future/

+2
source share

All Articles