【问题标题】:How to return values with nested Reader monads with cats?如何使用带有猫的嵌套 Reader monad 返回值?
【发布时间】:2022-01-25 05:35:12
【问题描述】:

有没有办法避免调用“run”方法两次而只在 main 方法中执行一次,或者这是在嵌套 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)
  }

【问题讨论】:

    标签: scala dependency-injection functional-programming scala-cats


    【解决方案1】:

    我不确定您为什么选择以这种方式定义服务,但如果您的问题是避免在 sumAndShow 中调用 run,您可以使用 @ 将您的 Reader/IO 提升为 ReaderTs 987654327@/liftF 并用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
    

    具有 Cats/Cats Effect 的现有类型类约束的替代通用实现,以及如何将 newtype 用于替代类型类实例:

    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
    }
    

    【讨论】:

    • 非常感谢。没有特殊原因我没有这样定义服务,但是既然你提到了,那么指定多个依赖项(包括由多个组件共享的依赖项)的最佳方法是什么?
    • 首先,Reader[Dependencies, IO[Int]] 是一个非常尴尬的类型,可以很容易地重构为ReaderT[IO, Dependencies, Int]Reader[Dependencies, Int]。我了解您希望为您的服务提供通用实现,但这会迫使您的 sum 不必要地包裹在 IO 中并依赖于 Dependencies ,它可能只返回 Int 并提升到 @ 987654340@ 需要时。
    • 您应该查看 Cats/Cats Effect 中的 ShowConsole 类型类。在案例类中包装服务很好,但如果您只需要一般地做事,则不需要将所有内容视为服务。要拥有相同类型的多个类型类实例,您可以将它们编码为新类型。这是一个示例:stackoverflow.com/questions/68569306/…
    • 然后,当您对类型类约束感到满意时,您可以阅读“无标签最终”和 MTL。
    • 我添加了一个示例,其中ShowServiceConsoleShow 替换,SumServiceSemigroup 替换。
    猜你喜欢
    • 1970-01-01
    • 2015-05-24
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2017-06-22
    • 2022-10-19
    • 2019-04-23
    • 2014-01-16
    相关资源
    最近更新 更多