Состав читателей для внедрения зависимостей в Scala - PullRequest
4 голосов
/ 13 марта 2019

Вот простой пример сервиса, методы которого возвращают читателя:

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

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

Вот сервис-потребитель, который принимает параметр 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
    })
}

Хорошо, чтобы использовать Service1_2.s12f1, у меня должна быть карта в списке параметров:

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

Вопрос: как реализовать Service1_2.s12f2:

trait Service1_2 {
  def s2f2 = ???
}

Для того, чтобы иметь возможность запустить его как:

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

Основная идея - отложить передачу зависимости к исполнению. Это должно позволить получить лучшую композицию и отложенное исполнение. Как заставить это работать? Каковы лучшие практики с Readers, если есть вложенные вызовы с такими зависимостями. Например, представьте себе сервис Service1_3, который в одном методе будет использовать Service1_2.s2f2 и Service1_1.s1f1

ОБНОВЛЕНИЕ Хорошо, я мог бы реализовать это, но выглядит сложнее:

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

Вопрос, есть ли лучший подход? Или хотя бы синтаксис? Потому что с несколькими уровнями зависимости это будет выглядеть странно.

1 Ответ

6 голосов
/ 13 марта 2019

Я бы, вероятно, «не торопил» читателя, чтобы вместо двух (или более) слоев читателей я имел бы 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
}

А потом:

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

Это работает точно так же, как ваш s2f2, за исключением того, что вместо s2.s2f2(2).run(s1).run(myMap) мы пишем s2.s2f2(2).run((s1, myMap)) или даже просто s2.s2f2(2).run(s1, myMap), используя адаптированные аргументы.

Преимущество этого подхода в том, что даже когда вы добавляете слои, вы можете объединить новых и предыдущих читателей в одно for -компанирование через local.

...