Scala: write for-comprehension with ReaderT and Option - scala

Here is example:
trait Service1 { def s1f = Option(10) }
trait Service2 {
type ReaderS1[A] = ReaderT[Option,Service1,A]
def s2f1: ReaderS1[Int] =
ReaderT(s1 =>
for {
r1 <- Option(1)
r2 <- s1.s1f
} yield r1 + r2
)
}
It works fine. I just want to rewrite s2f1 without ReaderT.apply method:
def s2f2:ReaderS1[Int] =
for {
r1 <- 1.pure[ReaderS1]
r2 <- //how to get result of Service1.s1f and compose it here
} yield r1 + r2
Here is a working example with Reader[...,Int], but not ReaderT[Option,...]:
import cats.data.Reader
trait Service1 { def s1f = 10 }
trait Service2 { def s2f = 20 }
trait Service3 {
def s3f1:Reader[Service1,Int] = Reader(1 + _.s1f)
def s3f2:Reader[Service2,Int] = Reader(2 + _.s2f)
import cats.syntax.applicative._ //for pure
type Env = (Service1, Service2)
type ReaderEnv[A] = Reader[Env,A] //needed to convert Int via pure
def c:ReaderEnv[Int] =
for {
s1 <- Reader((_:Env)._1)
r2 <- s1.s1f.pure[ReaderEnv]
r1 <- s3f2.local((_:Env)._2)
} yield r1 + r2
}
I want to get a similar syntax.

Try
import cats.syntax.applicative._
import cats.instances.option._
def s2f2: ReaderS1[Int] =
for {
r1 <- 1.pure[ReaderS1]
r2 <- ReaderT((_: Service1).s1f)
} yield r1 + r2

Related

Scala: ReaderT composition with different contexts and dependencies

Example of s3f1 and s3f2 functions that return different ReaderT:
type FailFast[A] = Either[List[String], A]
trait Service1 { def s1f:Option[Int] = Some(10) }
trait Service2 { def s2f:FailFast[Int] = Right(20) }
import cats.instances.option._
def s3f1: ReaderT[Option, Service1, Int] =
for {
r1 <- ReaderT((_: Service1).s1f)
} yield r1 + 1
import cats.syntax.applicative._
import cats.instances.either._
type ReaderService2FF[A] = ReaderT[FailFast, Service2, A]
def s3f2: ReaderService2FF[Int] =
for {
r1 <- ReaderT((_: Service2).s2f)
r2 <- 2.pure[ReaderService2FF]
} yield r1 + r2
I try to compose these two functions that return readers with different F[_] context and dependencies: ReaderT[Option, Service1, Int] and ReaderT[FailFast, Service2, Int]
I have to combine somehow the F[_] context, which means combine FailFast with Option. I assume, it makes sense to combine it to FailFast[Option]:
type Env = (Service1, Service2)
type FFOption[A] = FailFast[Option[A]]
type ReaderEnvFF[A] = ReaderT[FFOption, Env, A]
How to compose s3f1 and s3f2:
def c: ReaderEnvFF[Int] =
for {
r1 <- //s3f1
r2 <- //s3f2
} yield r1 + r2
Since you try to compose monads FailFast and Option in FFOption, you should use one more monad transformer, so FFOption[A] should be OptionT[FailFast, A] rather than just FailFast[Option[A]].
import cats.instances.option._
import cats.instances.either._
import cats.syntax.applicative._
import cats.syntax.either._
import cats.syntax.option._
type Env = (Service1, Service2)
type FFOption[A] = OptionT[FailFast, A]
type ReaderEnvFF[A] = ReaderT[FFOption, Env, A]
def c: ReaderEnvFF[Int] =
for {
r1 <- ReaderT[FFOption, Env, Int](p => OptionT(Either.right(s3f1.run(p._1))))
r2 <- ReaderT[FFOption, Env, Int](p => OptionT(s3f2.run(p._2).map(_.some)))
} yield r1 + r2
This can be rewritten with with local and mapF:
def c: ReaderEnvFF[Int] =
for {
r1 <- s3f1.local[Env](_._1).mapF[FFOption, Int](opt => OptionT(opt.asRight))
r2 <- s3f2.local[Env](_._2).mapF[FFOption, Int](ff => OptionT(ff.map(_.some)))
} yield r1 + r2

Getting compilation error when using for and yield

What is wrong with the following code snippet?
val loginInfoFuture: Future[LoginInfo] = credentialsProvider.authenticate(credentials)
for{loginInfo <- loginInfoFuture}{
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
I am seeing error in IDE - Error:(239, 17) ';' expected but 'yield' found.
} yield Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))
I tried a similar piece of code on REPL and that seem to work fine.
scala> import scala.concurrent.Future
import scala.concurrent.Future
scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global
scala> val f:Future[Int] = Future{1}
f: scala.concurrent.Future[Int] = Future(Success(1))
scala> for(f1 <- f) yield f1
res0: scala.concurrent.Future[Int] = Future(<not completed>)
scala>
For reference, below is the full function
def signInUser = silhouette.UserAwareAction.async { implicit request => {
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
jsonBody match {
case Some(json) => {
val userSignin: Option[UserSignin] = json.asOpt[UserSignin] //check if json conforms with UserProfile structure
userSignin match {
case Some(signinInfo) => { //format of JSON is correct
//Get signin info from JSON (email and password)
val credentials: Credentials = Credentials(signinInfo.signinInfo.email, signinInfo.signinInfo.password)
val authInfoRepository = new DelegableAuthInfoRepository(userRepo.passwordRepo)
val passwordHasherRegistory = new PasswordHasherRegistry(userRepo.passwordHasher)
val credentialsProvider = new CredentialsProvider(authInfoRepository, passwordHasherRegistory)
for{loginInfo <- loginInfoFuture}{ //for returns unit. Should use yield
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
}
case None => { //No signin info found
Future {
Ok(Json.toJson(JsonResultError("Invalid user. No Login info found")))
}
}
}
}
case None => {//NO Body
Future {
Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))
}
}
} //jsonBody match
}//async
}//def signin
for{loginInfo <- loginInfoFuture}{ //for returns unit. Should use yield
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
This is invalid. Your for/yield needs to be in the format:
for {
y <- z
x <- y
//etc
} yield {
//whatever
}
The println after the for but before the yield is throwing you. To get the result of the println inside the for/yield, you could to assign it to a value:
for {
y <- z
a = println(y) // will print out every y
x <- y
//etc
} yield {
//whatever
}
for/yield blocks are stupid like that. At least there are work-arounds though!
The following section is from scala's Future documentation:
def foo(): Unit = {
val f = Future { 5 }
val g = Future { 3 }
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} yield x + y
}
You on the other hand try to do this:
def foo(): Unit = {
val f = Future { 5 }
val g = Future { 3 }
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} {
println("whatever") // <<<<<<<<<
} yield x + y
}
The extra block of code that I point is what causing the compilation error which you did not add in your scala repl example.
This is how you can print within a Future:
def foo(): Unit = {
val f = Future {
println("5")
5
}
val g = Future {
println("3")
3
}
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} yield x + y
}
Error:(239, 17) ';' expected but 'yield' found.
simply means that the for loop definition is wrong
So either with yield
for{loginInfo <- loginInfoFuture
//other conditions and statements
} yield //value to be returned
or without yield
for(loginInfo <- loginInfoFuture){
//value updated
}
are correct for loop definitions

How to remove many layers of Future Option Future in the response

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)
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
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.

Play Framework: Transform Result to Json

how can I transform a result to Json in an reusable action?
Example:
object JsonAction {
def apply(block: Request[JsValue] => ???): Action[JsValue] = {
Action(BodyParsers.parse.json) { request =>
val result = block(request)
val finalResult = result.copy(body = Json.toJson(result.body))
finalResult
}
}
}
in my controller:
def index = JsonAction { req =>
Ok(new SomeModel(...))
}
The idea is to be able to seperate the result model and the representation as json (or xml for example).
I can not find a nice solution to this...
Something like this?
import play.api.libs.json._
import play.api.mvc._
object JsonAction extends Results {
def apply[A, B](block: A => B)(implicit reads: Reads[A], writes: Writes[B]): Action[JsValue] =
Action(BodyParsers.parse.json) { request =>
val result = for {
a <- Json.fromJson(request.body).asOpt
b = block(a)
} yield Ok(Json.toJson(b))
result getOrElse InternalServerError(???)
}
}
or you want to manually define resulting status in your block
object JsonAction extends Results {
def apply[A, B](block: A => (Option[(Status, B)]),
noneStatus: Result = BadRequest("could not parse json"))
(implicit reads: Reads[A], writes: Writes[B]): Action[JsValue] =
Action(BodyParsers.parse.json) { request =>
val result = for {
a <- Json.fromJson(request.body).asOpt
(status, b) <- block(a)
} yield status(Json.toJson(b))
result getOrElse noneStatus
}
}
object MathController {
import JsonAction._
def squareEquasion = JsonAction[Map[String, Double], Set[Double]] { map =>
for {a <- map get "a"
b <- map get "b"
c <- map get "c"
d = b * b - 4 * a * c} yield d match {
case d if d < 0 => (InternalServerError, Set.empty[Double])
case d if d == 0 => (Ok, Set(-b / 2 * a))
case d if d > 0 => (Ok, Set(1, -1) map (q => (-b + q * math.sqrt(d)) / 2 * a))
}
}
}
and final attempt - here we are providing instance of http.Writeable implicitly using an json.Writes instance and converting value to JSON inside this instance, so we could use Result builders ad-hock. This actually could cause some ambiguity if type have it's own Writeable instance (e.g. String):
import play.api.http.Writeable
import play.api.libs.json._
import play.api.mvc._
import scala.language.implicitConversions
object JsonAction {
private object Res extends Results
implicit def jsonWriteable[T](implicit writes: Writes[T]): Writeable[T] = {
val jsonWriteable = implicitly[Writeable[JsValue]]
def transform(obj: T) = jsonWriteable.transform(Json.toJson(obj))
new Writeable[T](transform, jsonWriteable.contentType)
}
def apply[A, B](block: A => Option[Result], noneStatus: Result = Res.BadRequest("could not parse json"))
(implicit reads: Reads[A], writes: Writes[B]): Action[JsValue] =
Action(BodyParsers.parse.json) { request =>
val result = for {
a <- Json.fromJson(request.body).asOpt
result <- block(a)
} yield result
result getOrElse noneStatus
}
}
object MathController extends Results{
import JsonAction._
def squareEquasion = JsonAction[Map[String, Double], Set[Double]] { map =>
for {a <- map get "a"
b <- map get "b"
c <- map get "c"
d = b * b - 4 * a * c} yield d match {
case d if d < 0 => InternalServerError("No answer")
case d if d == 0 => Ok(Set(-b / 2 * a))
case d if d > 0 => Ok(Set(1, -1) map (q => (-b + q * math.sqrt(d)) / 2 * a))
}
}
}

Why this simple Scala for comprehension does not execute the futures?

I'm stuck figuring out why this does not work:
import scala.concurrent.future
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
object FutureTest {
def main(args: Array[String]) {
val result1 = future("a")
val result2 = future("b")
val result3 = future("c")
val res = for {
r1 <- result1
r2 <- result2
r3 <- result3
} yield (r1 + r2 + r3)
for { r <- res} yield(println(r))
}
}
I'm expecting this to print "abc", but nothing really happens.
You are executing a stand alone program and the problem is that the main thread is terminated before the future can complete, to see something you could use this:
import scala.concurrent.future
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
object FutureTest {
def main(args: Array[String]) {
val result1 = future("a")
val result2 = future("b")
val result3 = future("c")
val res = for {
r1 <- result1
r2 <- result2
r3 <- result3
} yield (r1 + r2 + r3)
val printing = for { r <- res} yield(println(r))
Await.ready(printing, Duration.Inf)
}
}