Scala: ReaderT composition with different contexts and dependencies - scala

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

Related

How to return values with nested Reader monads with cats?

Is there a way to avoid calling the "run" method twice and doing it just once from the main method or this is the right way to do it in nested Readers?
case class Dependencies(showService: ShowService, sumService: SumService)
class ShowService {
def show(s: String): IO[Unit] = IO {println(s)}
}
class SumService() {
def sum(a: Int, b: Int): Reader[Dependencies, IO[Int]] = Reader {_ => IO {a + b} }
}
object ModuleA {
def sumAndShow: ReaderT[IO, Dependencies, Unit] = for {
x <- ReaderT[IO, Dependencies, Int] (deps => deps.sumService.sum(10, 10).run(deps))
r <- ReaderT[IO, Dependencies, Unit] (deps => deps.showService.show(x.toString))
} yield r
}
override def run(args: List[String]): IO[ExitCode] = {
val dependencies = Dependencies(new ShowService, new SumService)
ModuleA.sumAndShow.run(dependencies) *> IO(ExitCode.Success)
}
I'm not sure why you choose to define your services this way but if your question is to avoid calling run in sumAndShow you could lift your Reader/IO into ReaderTs with lift/liftF and compose them with a ReaderT.ask:
def sumAndShow: ReaderT[IO, Dependencies, Unit] =
for {
deps <- ReaderT.ask[IO, Dependencies]
x <- deps.sumService.sum(10, 10).lift[IO].flatMap(ReaderT.liftF)
r <- ReaderT.liftF(deps.showService.show(x.toString))
} yield r
Alternative generic implementation with existing typeclass constraints with Cats/Cats Effect, and also how newtype could be used for alternative typeclass instances:
import cats.implicits._
import cats.effect._
import cats.effect.std.Console
import cats.{Semigroup, Show}
import io.estatico.newtype.macros.newtype
object Main extends IOApp {
#newtype case class MInt(value: Int)
object MInt {
implicit val multiplicativeSemigroup: Semigroup[MInt] =
deriving(Semigroup.instance(_ * _))
implicit val show: Show[MInt] = deriving
}
def sumAndShow[F[_]: Console, A: Show: Semigroup](a: A, b: A): F[Unit] =
Console[F].println(a |+| b)
override def run(args: List[String]): IO[ExitCode] =
for {
_ <- sumAndShow[IO, Int](10, 10) //20
//you can also "inject" default/custom typeclass "dependencies" explicitly
_ <- sumAndShow[IO, Int](10, 10)(Console[IO], Show[Int], Semigroup.instance(_ * _)) //100
//custom typeclass instance with newtype
_ <- sumAndShow[IO, MInt](MInt(10), MInt(10)) //100
_ <- sumAndShow[IO, String]("Hello", "World") //HelloWorld
} yield ExitCode.Success
}

Scala-cats: reader-composition

import cats.data.ReaderT
import cats.instances.either._
trait Service1
trait Service2
case class Cats(name:String)
type FailFast[A] = Either[List[String], A]
type Env = (Service1, Service2, Cats)
type ReaderEnvFF[A] = ReaderT[FailFast, Env, A]
def toReaderEnvFF[A](input:A):ReaderEnvFF[A] =
ReaderT((_:Env) => Right(input))
def c:ReaderEnvFF[Cats] =
for {
cats <- toReaderEnvFF((_:Env)._3)
} yield cats // This line is 26
Error:
Error:(26, 11) type mismatch; found : T1.this.Env =>
com.savdev.Cats
(which expands to) ((com.savdev.Service1, com.savdev.Service2, com.savdev.Cats)) => com.savdev.Cats required: com.savdev.Cats }
yield cats
Can you please explain, why cats is not com.savdev.Cats? And why in the error, it says that it is expanded to a function with return method [Cats], bot not FailFast[Cats]
I try to apply exactly the same logic as here:
trait Service1 { def s1f = Option(10) }
trait Service2 {
type ReaderS1[A] = ReaderT[Option,Service1,A]
import cats.syntax.applicative._
import cats.instances.option._
def s2f:ReaderS1[Int] =
for {
r2 <- ReaderT((_: Service1).s1f)
r1 <- 1.pure[ReaderS1]
} yield r1 + r2
}
In this example I could convert function Service1.s1f to its result r2 and it works fine. Why I cannot, for instance write something like:
for {
cats <- ReaderT((_:Env)._3)
...
toReaderEnvFF((_: Env)._3) in cats <- toReaderEnvFF((_: Env)._3) is actually toReaderEnvFF[A]((_: Env)._3) for some type A. What is A now? Since (_: Env)._3 (aka input in toReaderEnvFF) is of type Env => Cats then type A is Env => Cats. So toReaderEnvFF((_: Env)._3) is of type ReaderEnvFF[Env => Cats] and cats in cats <- toReaderEnvFF((_: Env)._3) is of type Env => Cats.
In x <- SomeMonad[T] variable x is of type T (now SomeMonad is ReaderEnvFF, T is Env => Cats).
ReaderT((_: Service1).s1f) in your second example is of type ReaderT[Option, Service1, Int] so r2 in r2 <- ReaderT((_: Service1).s1f) is of type Int. But in your first example toReaderEnvFF((_: Env)._3) is of type ReaderEnvFF[Env => Cats] aka ReaderT[FailFast, Env, Env => Cats] so cats in cats <- toReaderEnvFF((_: Env)._3) is of type Env => Cats. That's the difference.
If you want to work with ReaderEnvFF[Cats] then you should change cats <- toReaderEnvFF(???). For example
def c:ReaderEnvFF[Cats] =
for {
cats <- toReaderEnvFF(Cats("aaa"))
} yield cats

Scala: write for-comprehension with ReaderT and Option

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

Get a list of field values of the same type in a case class

I have a class like this, that is giving detailed answer to what part of the overlaying algorithm went wrong if someone is interested and I want to enhance it in the future with many other possible invalidities in data.
case class Validity(grouping: Boolean = true,
matchingValuesValidation: Boolean = true) {
def compute: Boolean =
List(grouping,
matchingValuesValidation
).forall(identity)
def merge(ot: Validity): Validity =
Validity(
grouping && ot.grouping,
matchingValuesValidation && ot.matchingValuesValidation
)
}
I know all the fields will be Boolean, computation won't change. I would like to make methods compute and merge somehow iterate through all fields, so if I enhance it, there is no need to do it 3 times. If I use Map, I can add whatever key -> value pair I want and that is not desirable, I want to keep the structure. At the same time, I would like to enhance the class by simply adding another Boolean parameter to the class. Any ideas are greatly appreciated, thanks in advance
You know what? YOLO.
case class Validity(grouping: Boolean = true, matchingValuesValidation: Boolean = true) {
def values: List[Boolean] = {
import scala.reflect.runtime.universe._
val fields = typeOf[Validity].members.collect { case m: MethodSymbol if m.isCaseAccessor => m }.toList
val mirror = runtimeMirror(this.getClass.getClassLoader)
fields.map(mirror.reflect(this).reflectField(_).get.asInstanceOf[Boolean])
}
def compute: Boolean = values.forall(identity)
def merge(ot: Validity) = Validity.fromValues(this.values.zip(ot.values).map(v => v._1 && v._2).reverse)
}
object Validity {
def fromValues(values: List[Boolean]): Validity = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(Validity.getClass.getClassLoader)
val constructorSymbol = typeOf[Validity].decl(termNames.CONSTRUCTOR).asMethod
val classSymbol = mirror.reflectClass(typeOf[Validity].typeSymbol.asClass)
val constructor = classSymbol.reflectConstructor(constructorSymbol)
constructor(values:_*).asInstanceOf[Validity]
}
}
Instead of reflection you can use shapeless to do the heavy lifting for you:
import shapeless.ops.hlist.{Mapper, Zip}
import shapeless.{::, Generic, HList, HNil, Poly1}
private sealed trait lowPrioMergerMapper extends Poly1 {
implicit def mapperT[T]: Case.Aux[(T, T), T] = at[(T, T)] { case (v1, _) => v1}
}
private object MergerMapper extends lowPrioMergerMapper {
implicit def mapperBoolean: Case.Aux[(Boolean, Boolean), Boolean] = at[(Boolean, Boolean)] { case (v1, v2) => v1 && v2 }
}
def booleanFieldMerger[T, HL <: HList, ZOut <: HList](a: T, b: T)(
implicit
gen: Generic.Aux[T, HL],
zipWith: Zip.Aux[HL :: HL :: HNil, ZOut],
mapper: Mapper.Aux[MergerMapper.type ,ZOut, HL]
): T = {
val aRepr = gen.to(a)
val bRepr = gen.to(b)
gen.from((aRepr zip bRepr) map MergerMapper)
}
And the usage would be like:
val validity = Validity()
val validity2 = Validity(a = false)
val validity3 = Validity(b = false)
val validity4 = Validity(a = false, b = false)
booleanFieldMerger(validity,validity2) shouldBe validity2
booleanFieldMerger(validity2,validity3) shouldBe validity4
List(validity2,validity3).fold(validity)((a,b) => booleanFieldMerger(a,b)) shouldBe validity4
And for evaluation of the fields you can use:
import shapeless.HList
import shapeless.ops.hlist._
def evaluateBooleanFields[T, HL <: HList](input: T)(
implicit
gen: Generic.Aux[T, HL],
toTrav: ToList[HL,Any]): Boolean = {
gen.to(input).toList.foldLeft(true){
case (acc, e: Boolean) => acc && e
case (acc, _) => acc}
}
And the usage would be the same as above:
evaluateBooleanFields(validity) shouldBe true
evaluateBooleanFields(validity2) shouldBe false

Left flatMap on EitherT

Let's say I have functions which return Future[Either[_, _] and I want to apply some of these functions in case of failures, that means apply them only to left side. The simplified example is:
def operation1: Future[Either[String, Int]] = Future.successful(Right(5))
def operation2: Future[Either[String, Int]] = Future.successful(Left("error"))
def operation2FallBackWork = Future.successful{
println("Doing some revert stuff")
Left("Error happened, but reverting was successful")
}
val res = for {
res1 <- EitherT.fromEither(operation1)
res2 <- EitherT.fromEither(operation2)//.leftFlatMap(operation2FallBackWork) -????
} yield res1 + res2
Await.result(res.toEither, 5 seconds)
How to achieve that?
The closest thing to a leftFlatMap is MonadError's handleError, which has exactly the signature you'd expect from something called leftFlatMap (although note that you'll need to change the fallback operation to an EitherT and provide a constant function instead of passing it as-is). You can use the EitherT instance directly like this:
import scala.concurrent.{ Await, Future }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scalaz._, Scalaz._
def operation1: Future[Either[String, Int]] = Future.successful(Right(5))
def operation2: Future[Either[String, Int]] = Future.successful(Left("error"))
def operation2FallBack: EitherT[Future, String, Int] = EitherT(
Future.successful {
println("Doing some revert stuff")
"Error happened, but reverting was successful".left
}
)
val E: MonadError[({ type L[x] = EitherT[Future, String, x] })#L, String] =
implicitly
val res = for {
a <- EitherT.fromEither(operation1)
b <- E.handleError(EitherT.fromEither(operation2))(_ => operation2FallBack)
} yield a + b
Await.result(res.toEither, 5.seconds)
You can also use the syntax provided by MonadError to make it look like EitherT has a handleError method, although it takes a bit more ceremony to get the Scala compiler to recognize that your operations have the right shape:
import scala.concurrent.{ Await, Future }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scalaz._, Scalaz._
type FE[x] = EitherT[Future, String, x]
def operation1: FE[Int] = EitherT(Future.successful(5.right))
def operation2: FE[Int] = EitherT(Future.successful("error".left))
def operation2FallBack: FE[Int] = EitherT(
Future.successful {
println("Doing some revert stuff")
"Error happened, but reverting was successful".left
}
)
val res = for {
a <- operation1
b <- operation2.handleError(_ => operation2FallBack)
} yield a + b
Await.result(res.toEither, 5.seconds)
I'd prefer this second version, but it's a matter of style and taste.