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)
}
1 Answer 1
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 ReaderT
s 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
}
6 Comments
Reader[Dependencies, IO[Int]]
is really awkward type which could have been easily refactored to ReaderT[IO, Dependencies, Int]
or Reader[Dependencies, Int]
. I understand that you want generic implementation for your services but it kind of forces your sum
to be unnecessarily wrapped in an IO
and depend on Dependencies
where it could have just return an Int
and get lifted into a ReaderT
when needed.Show
and Console
typeclasses in Cats/Cats Effect. Wrapping services in case classes is fine but you don't need to view everything as a service if all you need is to do things generically. To have multiple typeclasses instances for the same types you can encode them as new types. Here's an example: stackoverflow.com/questions/68569306/… ShowService
is replaced by Console
and Show
, and SumService
is replaced by Semigroup
.Explore related questions
See similar questions with these tags.