Scala中依赖注入的阅读器的组成

Ale*_*ndr 4 dependency-injection scala composition reader-monad scala-cats

这是一个简单的服务示例,其方法返回reader:

trait Service1_1{
  def s1f1:Reader[Map[String,Int],Int] =
    Reader(_("name"))

  def s1f2:Reader[Map[String,Int],Int] =
    Reader(_("age"))
}
Run Code Online (Sandbox Code Playgroud)

这是一个服务使用者,它接受参数,映射并返回读取器本身:

trait Service1_2 {
  def s12f1(i:Int, map:Map[String,Int]):Reader[Service1_1, Int] =
    Reader(s => {
      val r = for {
        r1 <- s.s1f1
        r2 <- s.s1f2
      } yield r1 + r2
      r.run(map) + i
    })
}
Run Code Online (Sandbox Code Playgroud)

好的,要使用Service1_2.s12f1,我必须在参数列表中具有map:

object s1 extends Service1_1
object s2 extends Service1_2
val r = s2.s12f1(3, Map("age"-> 1, "name"-> 2)).run(s1)
Run Code Online (Sandbox Code Playgroud)

问题:如何实施Service1_2.s12f2

trait Service1_2 {
  def s2f2 = ???
}
Run Code Online (Sandbox Code Playgroud)

为了能够像这样运行它:

s2.s2f2(2)
  .run(s1)
  .run(Map("age"-> 1, "name"-> 2))
Run Code Online (Sandbox Code Playgroud)

主要思想是推迟将依赖传递给执行。这应该允许获得更好的组合和推迟执行。如何运作?如果存在具有此类依赖关系的嵌套调用,则使用Readers的最佳做法是什么。例如,假设服务,Service1_3在一种方法中,将同时使用Service1_2.s2f2Service1_1.s1f1

UPDATE,好的,我可以实现它,但是看起来很复杂:

  def s2f2(i:Int): Reader[Service1_1, Reader[Map[String,Int],Int]] =
    Reader(s => Reader(map => {
      val r = for {
        r1 <- s.s1f1
        r2 <- s.s1f2
      } yield r1 + r2
      r.run(map) + i
    }))
Run Code Online (Sandbox Code Playgroud)

问题是,有没有更好的方法?或者至少是语法?由于具有多个级别的依赖性,因此看起来很奇怪。

Tra*_*own 6

我可能会使读者“毫不费力”,因此,我将拥有一个n元组作为环境,而不是拥有两层(或更多层)的读者。然后,您可以使用将“小型”读者“提升”到当前水平local

例如,代替Reader[Service1_1, Reader[Map[String, Int], Int]]我将使用Reader[(Service1_1, Map[String, Int]), Int]

import cats.data.Reader

trait Service1_1{
  def s1f1: Reader[Map[String, Int], Int] = Reader(_("name"))
  def s1f2: Reader[Map[String, Int], Int] = Reader(_("age"))
}

trait Service1_2 {
  type Env = (Service1_1, Map[String,Int])

  def s2f2(i: Int): Reader[Env, Int] =
    for {
      s <- Reader((_: Env)._1)
      r1 <- s.s1f1.local((_: Env)._2)
      r2 <- s.s1f2.local((_: Env)._2)
    } yield r1 + r2 + i
}
Run Code Online (Sandbox Code Playgroud)

然后:

scala> object s1 extends Service1_1
defined object s1

scala> object s2 extends Service1_2
defined object s2

scala> s2.s2f2(2).run((s1, Map("age"-> 1, "name"-> 2)))
res0: cats.Id[Int] = 5
Run Code Online (Sandbox Code Playgroud)

这与您的工作原理完全相同,s2f2除了代替s2.s2f2(2).run(s1).run(myMap)我们编写s2.s2f2(2).run((s1, myMap)),甚至只是s2.s2f2(2).run(s1, myMap)使用改编的args。

这种方法的优点是,即使你加层,你可以在一个单一的组成在新老读者for通过-comprehension local

  • @Alexandr就像在读取器功能之前放置一个操作。它可以让您将“阅读器[A,B]”“扩展”到“阅读器[(A,C),B]`。 (2认同)
  • 我同意-一般而言,关于Cats相关主题的介绍性博文似乎很多,但在介绍其主题方面做得不好。一目了然[这个](https://blog.ssanj.net/posts/2017-06-12-reading-configuration-with-kleisli-arrows.html)似乎比大多数更好吗? (2认同)
  • 还有`Kleisli`文档:https://typelevel.org/cats/datatypes/kleisli.html (2认同)