IO and Future [Option] Monad Transformers

I am trying to figure out how to write this piece of code in an elegant, clean, functional style using floating point converters and monad transformers, but just can't get it around.

Just imagine that I have this simple API:

def findUuid(request: Request): Option[String] = ???
def findProfile(uuid: String): Future[Option[Profile]] = redisClient.get[Profile](uuid)

Using this API, I can easily write an impure function with an OptionT transformer as follows:

val profileT = for {
  uuid <- OptionT(Future.successful(findUuid(request)))
  profile <- OptionT(findProfile(uuid))
} yield profile
val profile: Future[Option[Profile]] = profileT.run

As you noticed, this function contains findProfile () with a side effect. I want to isolate this effect inside the IO monad and interpret it outside a pure function, but I don’t know how to combine all this together.

def findProfileIO(uuid: String): IO[Future[Option[Profile]]] = IO(findProfile(uuid))

val profileT = for {
  uuid <- OptionT(Future.successful(findUuid(request)))
  profile <- OptionT(findProfileIO(uuid)) //??? how to put Option inside of the IO[Future[Option]]
} yield profile
val profile = profileT.run //how to run transformer and interpret IO with the unsafePerformIO()??? 

How many tips on how to do this?

+4
source share
2

IO . Task , ! . : Task IO Scalaz?

Future Task, API :

def findUuid(request: Request): Option[String] = ??? 
def findProfile(uuid: String): Task[Option[Profile]] = ???

, Task , , findUuid Task IO.

OptionT:

val profileT = for {
  uuid <- OptionT(Task.now(findUuid(request)))
  profile <- OptionT(findProfileIO(uuid))
} yield profile

:

profileT.run.attemptRun

: Scalaz ↔

+3

, , - (Play 2.6).

- , PureAction ActionBuilder. !

, Action Play 2.6, .

FrontendController.scala:

def index = PureAction.pure { request =>
  val profileOpt = (for {
    uuid <- OptionT(Task.now(request.cookies.get("uuid").map(t => uuidKey(t.value))))
    profile <- OptionT(redis.get[Profile](uuid).asTask)
  } yield profile).run
  profileOpt.map { profileOpt =>
    Logger.info(profileOpt.map(p => s"User logged in - $p").getOrElse("New user, suggesting login"))
    Ok(views.html.index(profileOpt))
  }
}

Actions.scala

class PureAction @Inject()(parser: BodyParsers.Default)(implicit ec: ExecutionContext) extends ActionBuilderImpl(parser) {
  self =>
  def pure(block: Request[AnyContent] => Task[Result]): Action[AnyContent] = composeAction(new Action[AnyContent] {
    override def parser: BodyParser[AnyContent] = self.parser
    override def executionContext: ExecutionContext = self.ec
    override def apply(request: Request[AnyContent]): Future[Result] = {
      val taskResult = block(request)
      taskResult.asFuture //End of the world lives here
    }
  })
}

Converters.scala

- > - >

implicit class FuturePimped[+T](root: => Future[T]) {
  import scalaz.Scalaz._
  def asTask(implicit ec: ExecutionContext): Task[T] = {
    Task.async { register =>
      root.onComplete {
        case Success(v) => register(v.right)
        case Failure(ex) => register(ex.left)
      }
    }
  }
}

implicit class TaskPimped[T](root: => Task[T]) {
  import scalaz._
  val p: Promise[T] = Promise()
  def asFuture: Future[T] = {
    root.unsafePerformAsync {
      case -\/(ex) => p.failure(ex); ()
      case \/-(r) => p.success(r); ()
    }
    p.future
  }
}
+1

Source: https://habr.com/ru/post/1680643/


All Articles