简体   繁体   English

Scala中依赖注入的读者组成

[英]Composition of Readers for Dependency Injection in Scala

Here is a simple service example, whose methods return reader: 这是一个简单的服务示例,其方法返回读者:

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

  def s1f2:Reader[Map[String,Int],Int] =
    Reader(_("age"))
}

Here is a service-consumer, that accepts parameter, map, and also returns reader itself: 这是一个服务使用者,它接受参数,map,并返回阅读器本身:

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

Ok, to use Service1_2.s12f1 I must have map in the parameter list: 好的,要使用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)

The question: how to implement Service1_2.s12f2 : 问题:如何实现Service1_2.s12f2

trait Service1_2 {
  def s2f2 = ???
}

In order to be able to run it like: 为了能够运行它:

s2.s2f2(2)
  .run(s1)
  .run(Map("age"-> 1, "name"-> 2))

The main idea is to postpone passing of dependency to the execution. 主要思想是推迟将依赖关系传递给执行。 This should allow to get a better composition and deferred execution. 这应该允许获得更好的组合和延迟执行。 How to get it work? 如何让它工作? What are the best practices with Readers, in case there are nested calls with such dependencies. 如果存在具有此类依赖关系的嵌套调用,那么使用读者的最佳做法是什么? For instance, imagine service, Service1_3 , which in one method, will use both Service1_2.s2f2 and Service1_1.s1f1 例如,想象服务Service1_3 ,它在一种方法中将同时使用Service1_2.s2f2Service1_1.s1f1

UPDATE , ok, I could implement it, but it looks, over complicated: 更新 ,好吧,我可以实现它,但它看起来,复杂:

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

The question, is there a better approach? 问题是,有更好的方法吗? Or at least syntax? 或至少语法? Cause with several levels of dependency, it will look strange. 有几个级别的依赖,它会看起来很奇怪。

I'd probably "uncurry" the reader, so that instead of having two (or more) layers of readers, I'd have an n-tuple as the environment. 我可能会“解开”读者,所以我没有两个(或更多)读者层,而是有一个n元组作为环境。 Then you can "raise" smaller readers into the current level with local . 然后你就可以“养”小读者到当前的水平与local

For example, instead of Reader[Service1_1, Reader[Map[String, Int], Int]] I'd use Reader[(Service1_1, Map[String, Int]), Int] : 例如,我使用Reader[(Service1_1, Map[String, Int]), Int]代替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
}

And then: 然后:

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

This works exactly the same as your s2f2 except that instead of s2.s2f2(2).run(s1).run(myMap) we write s2.s2f2(2).run((s1, myMap)) , or even just s2.s2f2(2).run(s1, myMap) using adapted args. 这与s2f2完全相同,除了s2.s2f2(2).run(s1).run(myMap)不是s2.s2f2(2).run((s1, myMap)) ,甚至只是s2.s2f2(2).run(s1, myMap)使用改编的args。

The advantage of this approach is that even as you add layers, you can compose the new and previous readers in a single for -comprehension via local . 这种方法的优点是,即使在添加图层时,您也可以通过local编写单个for comprehension中的新读者和以前的读者。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM