Pedro Alipio
Pedro Alipio

Reputation: 117

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

Upvotes: 0

Views: 258

Answers (1)

yangzai
yangzai

Reputation: 992

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
}

Upvotes: 1

Related Questions