Как интерпретировать окончательный DSL без тегов с помощью ZIO?

У меня есть окончательный DSL без тегов для построения простых математических выражений:

trait Entity[F[_]] {
  def empty: F[Int]
  def int(value: Int): F[Int]
}

trait Operation[F[_]] {
  def add(a: F[Int], b: F[Int]): F[Int]
}

Я хотел реализовать интерпретатор ZIO. На основе module-pattern руководства возможная реализация может выглядеть следующим образом:

type Entity = Has[Entity[UIO]]

object Entity {
  val test: ULayer[Entity] =
    ZLayer.succeed {
      new Entity[UIO] {
        override def empty: UIO[Int] =
          ZIO.succeed(0)

        override def int(value: Int): UIO[Int] =
          ZIO.succeed(value)
      }
    }

  def empty: URIO[Entity, Int] =
    ZIO.accessM(_.get.empty)

  def int(value: Int): URIO[Entity, Int] =
    ZIO.accessM(_.get.int(value))
}

type Operation = Has[Operation[UIO]]

object Operation {
  val test: ULayer[Operation] =
    ZLayer.succeed {
      new Operation[UIO] {
        override def add(a: UIO[Int], b: UIO[Int]): UIO[Int] =
          ZIO.tupled(a, b).map { case (x, y) => x + y }
      }
    }

  def add(a: UIO[Int], b: UIO[Int]): URIO[Operation, Int] =
    ZIO.accessM(_.get.add(a, b))
}

При построении выражений с этой реализацией необходимо неоднократно вызывать provideLayer следующим образом:

Operation.subtract(
  Entity.empty.provideLayer(Entity.test),
  Entity.int(10).provideLayer(Entity.test)
).provideLayer(Operation.test)

Это больше похоже на антипаттерн. Какой способ интерпретации DSL был бы самым идиоматичным или самым ZIO?


person Lando-L    schedule 07.12.2020    source источник
comment
def add(a: UIO[Int], b: UIO[Int]): UIO[Int] немного странно. Почему не add(a: Int, b: Int): UIO[Int])?   -  person Thilo    schedule 08.12.2020
comment
или, может быть, add(a: Term[Int], b: Term[Int]): Term[Int] для какого-то Term для кодирования ваших математических выражений? При чем здесь ZIO? ZIO описывает эффекты (которые затем можно запускать и сочинять, но больше не нужно заглядывать внутрь, чтобы понять, что они делают).   -  person Thilo    schedule 08.12.2020
comment
В отличие от примера, трейты Service определены где-то еще, и для их реализации я использую только ZIO. В том смысле, что их нельзя изменить. Для уточнения у них подпись def int(value: Int): F[Int] и def add(a: F[Int], b: F[Int]): F[Int]   -  person Lando-L    schedule 08.12.2020


Ответы (2)


Из вопроса не совсем понятно, чего вы пытаетесь достичь, но позвольте мне попытаться ответить.

  1. Параметр ZIO R не имеет прямого отношения к построению DSL. Как только вы создадите свой DSL, R потенциально может помочь вам эргономично передать его в ваши вычисления (но, вероятно, нет).

  2. DSL — не совсем точный термин, но все же маловероятно, что ZIO поможет вам его построить. DSL обычно основаны на простых интроспективных типах данных (так называемое начальное кодирование) или абстрактных типах данных с большим количеством F (окончательное кодирование). ZIO тип данных не является ни абстрактным, ни интроспективным.

person simpadjo    schedule 07.12.2020
comment
Спасибо за ваш ответ. Я упростил вариант использования, чтобы его было легче понять, но, возможно, необходима некоторая контекстная информация. Наконец-то у меня есть DSL без тегов, который я хочу интерпретировать с помощью ZIO. DSL имеет подпись def int(value: Int): F[Int] и def add(a: F[Int], b: F[Int]): F[Int]. - person Lando-L; 08.12.2020
comment
Я обновил вопрос. Я надеюсь, что это делает его более ясным. - person Lando-L; 08.12.2020

Возвращаясь к этому вопросу с лучшим пониманием ZIO, я нашел решение. Это обходной путь и не в духе ZIO, тем не менее, я подумал, что им стоит поделиться.

Я обновил реализацию операции ZIO:

type Operation = Has[Service[URIO[Entity, *]]]

object Operation {
  val live: ULayer[Operation] =
    ZLayer.succeed {
      new Service[URIO[Entity, *]] {
        override def add(a: URIO[Entity, Int])(b: URIO[Entity, Int]): URIO[Entity, Int] =
          a.zip(b).map { case (x, y) => x + y }
      }
    }
}

def add(a: URIO[Entity, Int])(b: URIO[Entity, Int]): URIO[Entity with Operation, Int] =
  ZIO.accessM(_.get[Service[URIO[Entity, *]]].add(a)(b))

Таким образом, Сущность и Операция могут быть объединены следующим образом:

operation.add(entity.int(5))(entity.int(37))
person Lando-L    schedule 18.02.2021